refactor: proper options inheritance

This commit is contained in:
Evan You 2018-10-16 19:10:08 -04:00
parent 2a4e158c76
commit b83cf4ea38
5 changed files with 100 additions and 79 deletions

View File

@ -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

View File

@ -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 { data } = instance.$options
const rawData = (instance._rawData = (data ? data.call(instance) : {}) as any)
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]
rawData[key] = (instance as any)[key]
}
}
}
instance.$data = observable(instance._rawData || {})
instance.$data = observable(rawData || {})
}

View File

@ -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 {

View File

@ -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)
}
}
}

View File

@ -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
}