refactor: use flat options on class

This commit is contained in:
Evan You 2018-10-09 20:22:29 -04:00
parent b527705928
commit da6f0d7adc
9 changed files with 92 additions and 102 deletions

View File

@ -95,10 +95,8 @@ describe('attribute fallthrough', () => {
}
class Child extends Component<{ foo: number }> {
static options = {
props: {
foo: Number
}
static props = {
foo: Number
}
updated() {
childUpdated()
@ -169,10 +167,8 @@ describe('attribute fallthrough', () => {
}
class Child extends Component {
static options = {
props: {
foo: Number
}
static props = {
foo: Number
}
updated() {
childUpdated()
@ -183,10 +179,8 @@ describe('attribute fallthrough', () => {
}
class GrandChild extends Component<{ foo: number }> {
static options = {
props: {
foo: Number
}
static props = {
foo: Number
}
updated() {
grandChildUpdated()

View File

@ -3,6 +3,7 @@ import { VNode, Slots, RenderNode, MountedVNode } from './vdom'
import {
Data,
ComponentOptions,
ComponentClassOptions,
ComponentPropsOptions,
WatchOptions
} from './componentOptions'
@ -10,10 +11,10 @@ import { setupWatcher } from './componentWatch'
import { Autorun, DebuggerEvent, ComputedGetter } from '@vue/observer'
import { nextTick } from '@vue/scheduler'
import { ErrorTypes } from './errorHandling'
import { resolveComponentOptions } from './componentUtils'
type Flatten<T> = { [K in keyof T]: T[K] }
export interface ComponentClass extends Flatten<typeof InternalComponent> {
export interface ComponentClass extends ComponentClassOptions {
options?: ComponentOptions
new <P extends object = {}, D extends object = {}>(): MergedComponent<P, D>
}
@ -30,15 +31,15 @@ export interface FunctionalComponent<P = {}> {
export type ComponentType = ComponentClass | FunctionalComponent
export interface ComponentInstance<P = {}, D = {}> extends InternalComponent {
constructor: ComponentClass
$vnode: MountedVNode
$data: D
$props: Readonly<P>
$attrs: Data
$computed: Data
$slots: Slots
$root: ComponentInstance
$children: ComponentInstance[]
$options: ComponentOptions<this>
data?(): Partial<D>
render(props: Readonly<P>, slots: Slots, attrs: Data): any
@ -70,8 +71,6 @@ export interface ComponentInstance<P = {}, D = {}> extends InternalComponent {
}
class InternalComponent {
public static options?: ComponentOptions
public get $el(): RenderNode | null {
return this.$vnode && this.$vnode.el
}
@ -81,12 +80,11 @@ class InternalComponent {
public $data: Data | null = null
public $props: Data | null = null
public $attrs: Data | null = null
public $computed: Data | null = null
public $slots: Slots | null = null
public $root: ComponentInstance | null = null
public $parent: ComponentInstance | null = null
public $children: ComponentInstance[] = []
public $options: any
public $options: ComponentOptions
public $refs: Record<string, ComponentInstance | RenderNode> = {}
public $proxy: any = null
public $forceUpdate: (() => void) | null = null
@ -103,12 +101,10 @@ class InternalComponent {
public _isVue: boolean = true
public _inactiveRoot: boolean = false
constructor(options?: ComponentOptions) {
this.$options = options || (this.constructor as any).options || EMPTY_OBJ
// root instance
if (options !== void 0) {
// mount this
}
constructor() {
this.$options =
(this.constructor as ComponentClass).options ||
resolveComponentOptions(this.constructor as ComponentClass)
}
$nextTick(fn: () => any): Promise<any> {

View File

@ -1,21 +1,12 @@
import { EMPTY_OBJ, NOOP } from './utils'
import { NOOP } from './utils'
import { computed, stop, ComputedGetter } from '@vue/observer'
import { ComponentClass, ComponentInstance } from './component'
import { ComponentComputedOptions } from './componentOptions'
const extractionCache: WeakMap<
ComponentClass,
ComponentComputedOptions
> = new WeakMap()
export function getComputedOptions(
export function resolveComputedOptions(
comp: ComponentClass
): ComponentComputedOptions {
let computedOptions = extractionCache.get(comp)
if (computedOptions) {
return computedOptions
}
computedOptions = {}
const computedOptions: ComponentComputedOptions = {}
const descriptors = Object.getOwnPropertyDescriptors(comp.prototype as any)
for (const key in descriptors) {
const d = descriptors[key]
@ -25,7 +16,6 @@ export function getComputedOptions(
// as it's already defined on the prototype
}
}
extractionCache.set(comp, computedOptions)
return computedOptions
}
@ -34,7 +24,6 @@ export function initializeComputed(
computedOptions: ComponentComputedOptions | undefined
) {
if (!computedOptions) {
instance.$computed = EMPTY_OBJ
return
}
const handles: Record<
@ -47,17 +36,6 @@ export function initializeComputed(
const getter = typeof option === 'function' ? option : option.get || NOOP
handles[key] = computed(getter, proxy)
}
instance.$computed = new Proxy(
{},
{
get(_, key: any) {
if (handles.hasOwnProperty(key)) {
return handles[key]()
}
}
// TODO should be readonly
}
)
}
export function teardownComputed(instance: ComponentInstance) {

View File

@ -3,14 +3,18 @@ import { Slots } from './vdom'
export type Data = Record<string, any>
export interface ComponentOptions<This = ComponentInstance> {
data?(): object
export interface ComponentClassOptions<This = ComponentInstance> {
props?: ComponentPropsOptions
computed?: ComponentComputedOptions<This>
watch?: ComponentWatchOptions<This>
render?: (this: This, props: Readonly<Data>, slots: Slots, attrs: Data) => any
inheritAttrs?: boolean
displayName?: string
inheritAttrs?: boolean
}
export interface ComponentOptions<This = ComponentInstance>
extends ComponentClassOptions<This> {
data?(): object
render?: (this: This, props: Readonly<Data>, slots: Slots, attrs: Data) => any
// TODO other options
readonly [key: string]: any
}

View File

@ -18,9 +18,10 @@ import {
export function initializeProps(
instance: ComponentInstance,
options: ComponentPropsOptions | undefined,
data: Data | null
) {
const { props, attrs } = resolveProps(data, instance.$options.props)
const { props, attrs } = resolveProps(data, options)
instance.$props = immutable(props || {})
instance.$attrs = immutable(attrs || {})
}
@ -32,7 +33,7 @@ export function updateProps(instance: ComponentInstance, nextData: Data) {
if (nextData != null) {
const { props: nextProps, attrs: nextAttrs } = resolveProps(
nextData,
instance.$options.props
instance.constructor.props
)
// unlock to temporarily allow mutatiing props
unlock()

View File

@ -25,8 +25,8 @@ const renderProxyHandlers = {
// data
return target.$data[key]
} else if (
target.$options.props != null &&
target.$options.props.hasOwnProperty(key)
target.constructor.props != null &&
target.constructor.props.hasOwnProperty(key)
) {
// props are only proxied if declared
return target.$props[key]
@ -61,8 +61,8 @@ const renderProxyHandlers = {
return false
}
if (
target.$options.props != null &&
target.$options.props.hasOwnProperty(key)
target.constructor.props != null &&
target.constructor.props.hasOwnProperty(key)
) {
// TODO warn props are immutable
return false

View File

@ -8,7 +8,7 @@ import { initializeState } from './componentState'
import { initializeProps } from './componentProps'
import {
initializeComputed,
getComputedOptions,
resolveComputedOptions,
teardownComputed
} from './componentComputed'
import { initializeWatch, teardownWatch } from './componentWatch'
@ -40,11 +40,10 @@ export function createComponentInstance(
if (instance.beforeCreate) {
instance.beforeCreate.call(proxy)
}
// TODO provide/inject
initializeProps(instance, vnode.data)
initializeProps(instance, Component.props, vnode.data)
initializeState(instance)
initializeComputed(instance, getComputedOptions(Component))
initializeWatch(instance, instance.$options.watch)
initializeComputed(instance, Component.computed)
initializeWatch(instance, Component.watch)
instance.$slots = vnode.slots || EMPTY_OBJ
if (instance.created) {
instance.created.call(proxy)
@ -75,7 +74,7 @@ export function renderInstanceRoot(instance: ComponentInstance): VNode {
vnode,
instance.$parentVNode,
instance.$attrs,
instance.$options.inheritAttrs
instance.constructor.inheritAttrs
)
}
@ -171,36 +170,40 @@ export function createComponentClassFromOptions(
options: ComponentOptions
): ComponentClass {
class AnonymousComponent extends Component {
constructor() {
super()
this.$options = options
}
static options = options
}
const proto = AnonymousComponent.prototype as any
for (const key in options) {
const value = options[key]
// name -> displayName
if (__COMPAT__ && key === 'name') {
options.displayName = options.name
}
if (typeof value === 'function') {
if (__COMPAT__ && key === 'render') {
proto[key] = function() {
return value.call(this, h)
if (key === 'name') {
AnonymousComponent.displayName = options.name
} else if (typeof value === 'function') {
if (__COMPAT__) {
if (key === 'render') {
proto[key] = function() {
return value.call(this, h)
}
} else if (key === 'beforeDestroy') {
proto.beforeUnmount = value
} else if (key === 'destroyed') {
proto.unmounted = value
}
} else {
proto[key] = value
}
}
if (key === 'computed') {
const isGet = typeof value === 'function'
Object.defineProperty(proto, key, {
configurable: true,
get: isGet ? value : value.get,
set: isGet ? undefined : value.set
})
}
if (key === 'methods') {
} else if (key === 'computed') {
AnonymousComponent.computed = value
for (const computedKey in value) {
const computed = value[computedKey]
const isGet = typeof computed === 'function'
Object.defineProperty(proto, computedKey, {
configurable: true,
get: isGet ? computed : computed.get,
set: isGet ? undefined : computed.set
})
}
} else if (key === 'methods') {
for (const method in value) {
if (__DEV__ && proto.hasOwnProperty(method)) {
console.warn(
@ -210,7 +213,23 @@ export function createComponentClassFromOptions(
}
proto[method] = value[method]
}
} else {
;(AnonymousComponent as any)[key] = value
}
}
return AnonymousComponent as ComponentClass
}
export function resolveComponentOptions(
Component: ComponentClass
): ComponentOptions {
const keys = Object.keys(Component)
const options = {} as any
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
options[key] = (Component as any)[key]
}
Component.computed = options.computed = resolveComputedOptions(Component)
Component.options = options
return options
}

View File

@ -9,6 +9,16 @@ interface ProviderProps {
}
export class Provide extends Component<ProviderProps> {
static props = {
id: {
type: [String, Symbol],
required: true
},
value: {
required: true
}
}
updateValue() {
// TS doesn't allow symbol as index :/
// https://github.com/Microsoft/TypeScript/issues/24587
@ -43,18 +53,6 @@ export class Provide extends Component<ProviderProps> {
}
}
Provide.options = {
props: {
id: {
type: [String, Symbol],
required: true
},
value: {
required: true
}
}
}
export class Inject extends Component {
render(props: any, slots: any) {
return slots.default && slots.default(contextStore[props.id])

View File

@ -114,7 +114,7 @@ export class KeepAlive extends Component<KeepAliveProps> {
;(KeepAlive as any)[KeepAliveSymbol] = true
function getName(comp: ComponentClass): string | void {
return comp.options && comp.options.name
return comp.displayName || comp.name
}
function matches(pattern: MatchPattern, name: string): boolean {