import { Autorun, DebuggerEvent, ComputedGetter } from '@vue/observer'
import { nextTick } from '@vue/scheduler'
import { ErrorTypes } from './errorHandling'
-import { resolveComponentOptions } from './componentUtils'
+import { initializeComponentInstance } from './componentUtils'
export interface ComponentClass extends ComponentClassOptions {
options?: ComponentOptions
public _inactiveRoot: boolean = false
constructor() {
- this.$options =
- (this.constructor as ComponentClass).options ||
- resolveComponentOptions(this.constructor as ComponentClass)
+ initializeComponentInstance(this as any)
}
$nextTick(fn: () => any): Promise<any> {
} from './componentOptions'
import { EMPTY_OBJ, camelize, hyphenate, capitalize } from './utils'
+const EMPTY_PROPS = { props: EMPTY_OBJ }
+
export function initializeProps(
instance: ComponentInstance,
options: ComponentPropsOptions | undefined,
instance.$attrs = immutable(attrs || {})
}
-export function updateProps(instance: ComponentInstance, nextData: Data) {
- // instance.$props and instance.$attrs are observables that should not be
- // replaced. Instead, we mutate them to match latest props, which will trigger
- // updates if any value that's been used in child component has changed.
- if (nextData != null) {
- const { props: nextProps, attrs: nextAttrs } = resolveProps(
- nextData,
- instance.constructor.props
- )
- // unlock to temporarily allow mutatiing props
- unlock()
- const props = instance.$props
- const rawProps = unwrap(props)
- for (const key in rawProps) {
- if (!nextProps.hasOwnProperty(key)) {
- delete (props as any)[key]
- }
- }
- for (const key in nextProps) {
- ;(props as any)[key] = nextProps[key]
- }
- if (nextAttrs) {
- const attrs = instance.$attrs
- const rawAttrs = unwrap(attrs)
- for (const key in rawAttrs) {
- if (!nextAttrs.hasOwnProperty(key)) {
- delete attrs[key]
- }
- }
- for (const key in nextAttrs) {
- attrs[key] = nextAttrs[key]
- }
- }
- lock()
- }
-}
-
-const EMPTY_PROPS = { props: EMPTY_OBJ }
-
// resolve raw VNode data.
// - filter out reserved keys (key, ref, slots)
// - extract class and style into $attrs (to be merged onto child
return { props, attrs }
}
+export function updateProps(instance: ComponentInstance, nextData: Data) {
+ // instance.$props and instance.$attrs are observables that should not be
+ // replaced. Instead, we mutate them to match latest props, which will trigger
+ // updates if any value that's been used in child component has changed.
+ if (nextData != null) {
+ const { props: nextProps, attrs: nextAttrs } = resolveProps(
+ nextData,
+ instance.constructor.props
+ )
+ // unlock to temporarily allow mutatiing props
+ unlock()
+ const props = instance.$props
+ const rawProps = unwrap(props)
+ for (const key in rawProps) {
+ if (!nextProps.hasOwnProperty(key)) {
+ delete (props as any)[key]
+ }
+ }
+ for (const key in nextProps) {
+ ;(props as any)[key] = nextProps[key]
+ }
+ if (nextAttrs) {
+ const attrs = instance.$attrs
+ const rawAttrs = unwrap(attrs)
+ for (const key in rawAttrs) {
+ if (!nextAttrs.hasOwnProperty(key)) {
+ delete attrs[key]
+ }
+ }
+ for (const key in nextAttrs) {
+ attrs[key] = nextAttrs[key]
+ }
+ }
+ lock()
+ }
+}
+
const enum BooleanFlags {
shouldCast = '1',
shouldCastTrue = '2'
-import { EMPTY_OBJ } from './utils'
+// import { EMPTY_OBJ } from './utils'
import { ComponentInstance } from './component'
import { observable } from '@vue/observer'
+const internalRE = /^_|^\$/
+
export function initializeState(instance: ComponentInstance) {
if (instance.data) {
instance._rawData = instance.data()
- instance.$data = observable(instance._rawData)
} else {
- instance.$data = EMPTY_OBJ
+ const keys = Object.keys(instance)
+ const data = (instance._rawData = {} as any)
+ for (let i = 0; i < keys.length; i++) {
+ const key = keys[i]
+ if (!internalRE.test(key)) {
+ data[key] = (instance as any)[key]
+ }
+ }
}
+ instance.$data = observable(instance._rawData || {})
}
import { createRenderProxy } from './componentProxy'
import { handleError, ErrorTypes } from './errorHandling'
+let currentVNode: VNode | null = null
+let currentContextVNode: MountedVNode | null = null
+
export function createComponentInstance(
vnode: VNode,
Component: ComponentClass,
contextVNode: MountedVNode | null
): ComponentInstance {
+ // component instance creation is done in two steps.
+ // first, `initializeComponentInstance` is called inside base component
+ // constructor as the instance is created so that
+ currentVNode = vnode
+ currentContextVNode = contextVNode
const instance = (vnode.children = new Component()) as ComponentInstance
- instance.$parentVNode = vnode as MountedVNode
+ // then we finish the initialization by collecting properties set on the
+ // instance
+ initializeState(instance)
+ initializeComputed(instance, Component.computed)
+ initializeWatch(instance, Component.watch)
+ instance.$slots = currentVNode.slots || EMPTY_OBJ
+ if (instance.created) {
+ instance.created.call(instance.$proxy)
+ }
+ currentVNode = currentContextVNode = null
+ return instance
+}
+
+// this is called inside the base component's constructor
+// it initializes all the way up to props so that they are available
+// inside the extended component's constructor
+export function initializeComponentInstance(instance: ComponentInstance) {
+ if (__DEV__ && currentVNode === null) {
+ throw new Error(
+ `Component classes are not meant to be manually instantiated.`
+ )
+ }
+
+ instance.$options =
+ instance.constructor.options ||
+ resolveComponentOptions(instance.constructor)
+ instance.$parentVNode = currentVNode as MountedVNode
// renderProxy
const proxy = (instance.$proxy = createRenderProxy(instance))
- // pointer management
- if (contextVNode !== null) {
+ // parent chain management
+ if (currentContextVNode !== null) {
// locate first non-functional parent
while (
- contextVNode !== null &&
- contextVNode.flags & VNodeFlags.COMPONENT_FUNCTIONAL &&
- contextVNode.contextVNode !== null
+ currentContextVNode !== null &&
+ currentContextVNode.flags & VNodeFlags.COMPONENT_FUNCTIONAL &&
+ currentContextVNode.contextVNode !== null
) {
- contextVNode = contextVNode.contextVNode as any
+ currentContextVNode = currentContextVNode.contextVNode as any
}
- const parentComponent = (contextVNode as VNode)
+ const parentComponent = (currentContextVNode as VNode)
.children as ComponentInstance
instance.$parent = parentComponent.$proxy
instance.$root = parentComponent.$root
instance.$root = proxy
}
- // lifecycle
+ // beforeCreate hook is called right in the constructor
if (instance.beforeCreate) {
instance.beforeCreate.call(proxy)
}
- initializeProps(instance, Component.props, vnode.data)
- initializeState(instance)
- initializeComputed(instance, Component.computed)
- initializeWatch(instance, Component.watch)
- instance.$slots = vnode.slots || EMPTY_OBJ
- if (instance.created) {
- instance.created.call(proxy)
- }
-
- return instance as ComponentInstance
+ initializeProps(
+ instance,
+ instance.constructor.props,
+ (currentVNode as VNode).data
+ )
}
export function renderInstanceRoot(instance: ComponentInstance): VNode {
export function resolveComponentOptions(
Component: ComponentClass
): ComponentOptions {
- const keys = Object.keys(Component)
+ const descriptors = Object.getOwnPropertyDescriptors(Component)
const options = {} as any
- for (let i = 0; i < keys.length; i++) {
- const key = keys[i]
- options[key] = (Component as any)[key]
+ for (const key in descriptors) {
+ const descriptor = descriptors[key]
+ if (descriptor.enumerable || descriptor.get) {
+ options[key] = descriptor.get ? descriptor.get() : descriptor.value
+ }
}
Component.computed = options.computed = resolveComputedOptions(Component)
Component.options = options
type AsyncComponentOptions = AsyncComponentFactory | AsyncComponentFullOptions
-interface AsyncContainerData {
- comp: ComponentType | null
- err: Error | null
- delayed: boolean
- timedOut: boolean
-}
-
export function createAsyncComponent(
options: AsyncComponentOptions
): ComponentClass {
error: errorComp
} = options
- return class AsyncContainer extends Component<{}, AsyncContainerData> {
- data() {
- return {
- comp: null,
- err: null,
- delayed: false,
- timedOut: false
- }
- }
+ return class AsyncContainer extends Component {
+ comp: ComponentType | null = null
+ err: Error | null = null
+ delayed: boolean = false
+ timedOut: boolean = false
// doing this in beforeMount so this is non-SSR only
beforeMount() {
h,
render,
nextTick,
- Component,
createComponentInstance,
createComponentClassFromOptions
} from '@vue/renderer-dom'
-// Note: typing for this is intentionally loose, as it will be using 2.x types.
-class Vue extends Component {
+class Vue {
static h = h
static render = render
static nextTick = nextTick
+ // Note: typing for this is intentionally loose, as it will be using 2.x types.
constructor(options: any) {
- super()
- if (!options) {
- return
- }
-
- // in compat mode, h() can take an options object and will convert it
- // to a 3.x class-based component.
- const Component = createComponentClassFromOptions(options)
+ // convert it to a class
+ const Component = createComponentClassFromOptions(options || {})
const vnode = h(Component)
- // the component class is cached on the options object as ._normalized
const instance = createComponentInstance(vnode, Component, null)
- // set the instance on the vnode before mounting.
- // the mount function will skip creating a new instance if it finds an
- // existing one.
- vnode.children = instance
function mount(el: any) {
const dom = typeof el === 'string' ? document.querySelector(el) : el