From b83cf4ea3842e17af6fe237e7891031acc1b95cc Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 16 Oct 2018 19:10:08 -0400 Subject: [PATCH] refactor: proper options inheritance --- packages/core/src/componentOptions.ts | 31 +++++--- packages/core/src/componentState.ts | 19 ++--- packages/core/src/componentUtils.ts | 26 ++++--- packages/core/src/createRenderer.ts | 101 ++++++++++++++------------ packages/core/src/h.ts | 2 +- 5 files changed, 100 insertions(+), 79 deletions(-) diff --git a/packages/core/src/componentOptions.ts b/packages/core/src/componentOptions.ts index 2b033940a..385a2f6de 100644 --- a/packages/core/src/componentOptions.ts +++ b/packages/core/src/componentOptions.ts @@ -1,4 +1,5 @@ import { + Component, ComponentInstance, ComponentClass, APIMethods, @@ -104,22 +105,22 @@ export const reservedMethods: ReservedKeys = { // This is called in the base component constructor and the return value is // set on the instance as $options. export function resolveComponentOptionsFromClass( - Component: ComponentClass + Class: ComponentClass ): ComponentOptions { - if (Component.options) { - return Component.options + if (Class.hasOwnProperty('options')) { + return Class.options as ComponentOptions } - const staticDescriptors = Object.getOwnPropertyDescriptors(Component) - const options = {} as any + let options = {} as any + + const staticDescriptors = Object.getOwnPropertyDescriptors(Class) for (const key in staticDescriptors) { const { enumerable, get, value } = staticDescriptors[key] if (enumerable || get) { options[key] = get ? get() : value } } - const instanceDescriptors = Object.getOwnPropertyDescriptors( - Component.prototype - ) + + const instanceDescriptors = Object.getOwnPropertyDescriptors(Class.prototype) for (const key in instanceDescriptors) { const { get, value } = instanceDescriptors[key] if (get) { @@ -127,7 +128,7 @@ export function resolveComponentOptionsFromClass( ;(options.computed || (options.computed = {}))[key] = get // there's no need to do anything for the setter // as it's already defined on the prototype - } else if (isFunction(value)) { + } else if (isFunction(value) && key !== 'constructor') { if (key in reservedMethods) { options[key] = value } else { @@ -135,8 +136,16 @@ export function resolveComponentOptionsFromClass( } } } + options.props = normalizePropsOptions(options.props) - Component.options = options + + const ParentClass = Object.getPrototypeOf(Class) + if (ParentClass !== Component) { + const parentOptions = resolveComponentOptionsFromClass(ParentClass) + options = mergeComponentOptions(parentOptions, options) + } + + Class.options = options return options } @@ -154,7 +163,7 @@ export function mergeComponentOptions(to: any, from: any): ComponentOptions { if (key === 'data') { // for data we need to merge the returned value res[key] = function() { - return Object.assign(existing(), value()) + return Object.assign(existing.call(this), value.call(this)) } } else if (/^render|^errorCaptured/.test(key)) { // render, renderTracked, renderTriggered & errorCaptured diff --git a/packages/core/src/componentState.ts b/packages/core/src/componentState.ts index 5c1477ce6..594d8b3d3 100644 --- a/packages/core/src/componentState.ts +++ b/packages/core/src/componentState.ts @@ -4,17 +4,14 @@ import { observable } from '@vue/observer' const internalRE = /^_|^\$/ export function initializeState(instance: ComponentInstance) { - if (instance.data) { - instance._rawData = instance.data() - } else { - 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] - } + const { data } = instance.$options + const rawData = (instance._rawData = (data ? data.call(instance) : {}) as any) + const keys = Object.keys(instance) + for (let i = 0; i < keys.length; i++) { + const key = keys[i] + if (!internalRE.test(key)) { + rawData[key] = (instance as any)[key] } } - instance.$data = observable(instance._rawData || {}) + instance.$data = observable(rawData || {}) } diff --git a/packages/core/src/componentUtils.ts b/packages/core/src/componentUtils.ts index 3eb69f5a9..76a1e3e0a 100644 --- a/packages/core/src/componentUtils.ts +++ b/packages/core/src/componentUtils.ts @@ -38,15 +38,22 @@ export function createComponentInstance( currentVNode = vnode currentContextVNode = vnode.contextVNode const instance = (vnode.children = new Component() as ComponentInstance) + // then we finish the initialization by collecting properties set on the // instance + const { + $proxy, + $options: { created, computed, watch } + } = instance initializeState(instance) - initializeComputed(instance, instance.$options.computed) - initializeWatch(instance, instance.$options.watch) + initializeComputed(instance, computed) + initializeWatch(instance, watch) instance.$slots = currentVNode.slots || EMPTY_OBJ - if (instance.created) { - instance.created.call(instance.$proxy) + + if (created) { + created.call($proxy) } + currentVNode = currentContextVNode = null return instance } @@ -87,14 +94,11 @@ export function initializeComponentInstance(instance: ComponentInstance) { } // beforeCreate hook is called right in the constructor - if (instance.beforeCreate) { - instance.beforeCreate.call(proxy) + const { beforeCreate, props } = instance.$options + if (beforeCreate) { + beforeCreate.call(proxy) } - initializeProps( - instance, - instance.$options.props, - (currentVNode as VNode).data - ) + initializeProps(instance, props, (currentVNode as VNode).data) } export function renderInstanceRoot(instance: ComponentInstance): VNode { diff --git a/packages/core/src/createRenderer.ts b/packages/core/src/createRenderer.ts index 8ffffca35..963432e04 100644 --- a/packages/core/src/createRenderer.ts +++ b/packages/core/src/createRenderer.ts @@ -1167,8 +1167,13 @@ export function createRenderer(options: RendererOptions) { ;(instance as any).$unmount = unmountComponentInstance } - if (instance.beforeMount) { - instance.beforeMount.call(instance.$proxy) + const { + $proxy, + $options: { beforeMount, mounted, renderTracked, renderTriggered } + } = instance + + if (beforeMount) { + beforeMount.call($proxy) } const queueUpdate = (instance.$forceUpdate = () => { @@ -1200,33 +1205,26 @@ export function createRenderer(options: RendererOptions) { } instance._mounted = true - mountComponentInstanceCallbacks(instance, vnode.ref) + if (vnode.ref) { + mountRef(vnode.ref, $proxy) + } + if (mounted) { + lifecycleHooks.push(() => { + mounted.call($proxy) + }) + } } }, { scheduler: queueUpdate, - onTrack: instance.renderTracked, - onTrigger: instance.renderTriggered + onTrack: renderTracked, + onTrigger: renderTriggered } ) return vnode.el as RenderNode } - function mountComponentInstanceCallbacks( - instance: ComponentInstance, - ref: Ref | null - ) { - if (ref) { - mountRef(ref, instance.$proxy) - } - if (instance.mounted) { - lifecycleHooks.push(() => { - ;(instance as any).mounted.call(instance.$proxy) - }) - } - } - function updateComponentInstance( instance: ComponentInstance, isSVG: boolean @@ -1234,23 +1232,22 @@ export function createRenderer(options: RendererOptions) { if (__DEV__ && instance.$parentVNode) { pushWarningContext(instance.$parentVNode as VNode) } - const prevVNode = instance.$vnode - if (instance.beforeUpdate) { - instance.beforeUpdate.call(instance.$proxy, prevVNode) + const { + $vnode: prevVNode, + $parentVNode, + $proxy, + $options: { beforeUpdate, updated } + } = instance + if (beforeUpdate) { + beforeUpdate.call($proxy, prevVNode) } const nextVNode = (instance.$vnode = renderInstanceRoot( instance ) as MountedVNode) const container = platformParentNode(prevVNode.el) as RenderNode - patch( - prevVNode, - nextVNode, - container, - instance.$parentVNode as MountedVNode, - isSVG - ) + patch(prevVNode, nextVNode, container, $parentVNode as MountedVNode, isSVG) const el = nextVNode.el as RenderNode if (__COMPAT__) { @@ -1260,7 +1257,7 @@ export function createRenderer(options: RendererOptions) { // recursively update contextVNode el for nested HOCs if ((nextVNode.flags & VNodeFlags.PORTAL) === 0) { - let vnode = instance.$parentVNode + let vnode = $parentVNode while (vnode !== null) { if ((vnode.flags & VNodeFlags.COMPONENT) > 0) { vnode.el = el @@ -1269,14 +1266,14 @@ export function createRenderer(options: RendererOptions) { } } - if (instance.updated) { + if (updated) { // Because the child's update is executed by the scheduler and not // synchronously within the parent's update call, the child's updated hook // will be added to the queue AFTER the parent's, but they should be // invoked BEFORE the parent's. Therefore we add them to the head of the // queue instead. lifecycleHooks.unshift(() => { - ;(instance as any).updated.call(instance.$proxy, nextVNode) + updated.call($proxy, nextVNode) }) } @@ -1299,17 +1296,23 @@ export function createRenderer(options: RendererOptions) { if (instance._unmounted) { return } - if (instance.beforeUnmount) { - instance.beforeUnmount.call(instance.$proxy) + const { + $vnode, + $proxy, + _updateHandle, + $options: { beforeUnmount, unmounted } + } = instance + if (beforeUnmount) { + beforeUnmount.call($proxy) } - if (instance.$vnode) { - unmount(instance.$vnode) + if ($vnode) { + unmount($vnode) } - stop(instance._updateHandle) + stop(_updateHandle) teardownComponentInstance(instance) instance._unmounted = true - if (instance.unmounted) { - instance.unmounted.call(instance.$proxy) + if (unmounted) { + unmounted.call($proxy) } } @@ -1338,12 +1341,16 @@ export function createRenderer(options: RendererOptions) { } if (asRoot || !instance._inactiveRoot) { // 2. recursively call activated on child tree, depth-first - const { $children } = instance + const { + $children, + $proxy, + $options: { activated } + } = instance for (let i = 0; i < $children.length; i++) { callActivatedHook($children[i], false) } - if (instance.activated) { - instance.activated.call(instance.$proxy) + if (activated) { + activated.call($proxy) } } } @@ -1359,12 +1366,16 @@ export function createRenderer(options: RendererOptions) { } if (asRoot || !instance._inactiveRoot) { // 2. recursively call deactivated on child tree, depth-first - const { $children } = instance + const { + $children, + $proxy, + $options: { deactivated } + } = instance for (let i = 0; i < $children.length; i++) { callDeactivateHook($children[i], false) } - if (instance.deactivated) { - instance.deactivated.call(instance.$proxy) + if (deactivated) { + deactivated.call($proxy) } } } diff --git a/packages/core/src/h.ts b/packages/core/src/h.ts index f7323f31e..86ab1846a 100644 --- a/packages/core/src/h.ts +++ b/packages/core/src/h.ts @@ -99,7 +99,7 @@ interface createElement extends VNodeFactories { } export const h = ((tag: ElementType, data?: any, children?: any): VNode => { - if (isArray(data) || !isObject(data) || data._isVNode) { + if (data !== null && (isArray(data) || !isObject(data) || data._isVNode)) { children = data data = null }