}
class Child extends Component<{ foo: number }> {
- static options = {
- props: {
- foo: Number
- }
+ static props = {
+ foo: Number
}
updated() {
childUpdated()
}
class Child extends Component {
- static options = {
- props: {
- foo: Number
- }
+ static props = {
+ foo: Number
}
updated() {
childUpdated()
}
class GrandChild extends Component<{ foo: number }> {
- static options = {
- props: {
- foo: Number
- }
+ static props = {
+ foo: Number
}
updated() {
grandChildUpdated()
import {
Data,
ComponentOptions,
+ ComponentClassOptions,
ComponentPropsOptions,
WatchOptions
} from './componentOptions'
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>
}
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
}
class InternalComponent {
- public static options?: ComponentOptions
-
public get $el(): RenderNode | null {
return this.$vnode && this.$vnode.el
}
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
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> {
-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]
// as it's already defined on the prototype
}
}
- extractionCache.set(comp, computedOptions)
return computedOptions
}
computedOptions: ComponentComputedOptions | undefined
) {
if (!computedOptions) {
- instance.$computed = EMPTY_OBJ
return
}
const handles: Record<
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) {
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
}
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 || {})
}
if (nextData != null) {
const { props: nextProps, attrs: nextAttrs } = resolveProps(
nextData,
- instance.$options.props
+ instance.constructor.props
)
// unlock to temporarily allow mutatiing props
unlock()
// 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]
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
import { initializeProps } from './componentProps'
import {
initializeComputed,
- getComputedOptions,
+ resolveComputedOptions,
teardownComputed
} from './componentComputed'
import { initializeWatch, teardownWatch } from './componentWatch'
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)
vnode,
instance.$parentVNode,
instance.$attrs,
- instance.$options.inheritAttrs
+ instance.constructor.inheritAttrs
)
}
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(
}
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
+}
}
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
}
}
-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])
;(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 {