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 { import {
Component,
ComponentInstance, ComponentInstance,
ComponentClass, ComponentClass,
APIMethods, APIMethods,
@ -104,22 +105,22 @@ export const reservedMethods: ReservedKeys = {
// This is called in the base component constructor and the return value is // This is called in the base component constructor and the return value is
// set on the instance as $options. // set on the instance as $options.
export function resolveComponentOptionsFromClass( export function resolveComponentOptionsFromClass(
Component: ComponentClass Class: ComponentClass
): ComponentOptions { ): ComponentOptions {
if (Component.options) { if (Class.hasOwnProperty('options')) {
return Component.options return Class.options as ComponentOptions
} }
const staticDescriptors = Object.getOwnPropertyDescriptors(Component) let options = {} as any
const options = {} as any
const staticDescriptors = Object.getOwnPropertyDescriptors(Class)
for (const key in staticDescriptors) { for (const key in staticDescriptors) {
const { enumerable, get, value } = staticDescriptors[key] const { enumerable, get, value } = staticDescriptors[key]
if (enumerable || get) { if (enumerable || get) {
options[key] = get ? get() : value options[key] = get ? get() : value
} }
} }
const instanceDescriptors = Object.getOwnPropertyDescriptors(
Component.prototype const instanceDescriptors = Object.getOwnPropertyDescriptors(Class.prototype)
)
for (const key in instanceDescriptors) { for (const key in instanceDescriptors) {
const { get, value } = instanceDescriptors[key] const { get, value } = instanceDescriptors[key]
if (get) { if (get) {
@ -127,7 +128,7 @@ export function resolveComponentOptionsFromClass(
;(options.computed || (options.computed = {}))[key] = get ;(options.computed || (options.computed = {}))[key] = get
// there's no need to do anything for the setter // there's no need to do anything for the setter
// as it's already defined on the prototype // as it's already defined on the prototype
} else if (isFunction(value)) { } else if (isFunction(value) && key !== 'constructor') {
if (key in reservedMethods) { if (key in reservedMethods) {
options[key] = value options[key] = value
} else { } else {
@ -135,8 +136,16 @@ export function resolveComponentOptionsFromClass(
} }
} }
} }
options.props = normalizePropsOptions(options.props) 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 return options
} }
@ -154,7 +163,7 @@ export function mergeComponentOptions(to: any, from: any): ComponentOptions {
if (key === 'data') { if (key === 'data') {
// for data we need to merge the returned value // for data we need to merge the returned value
res[key] = function() { res[key] = function() {
return Object.assign(existing(), value()) return Object.assign(existing.call(this), value.call(this))
} }
} else if (/^render|^errorCaptured/.test(key)) { } else if (/^render|^errorCaptured/.test(key)) {
// render, renderTracked, renderTriggered & errorCaptured // render, renderTracked, renderTriggered & errorCaptured

View File

@ -4,17 +4,14 @@ import { observable } from '@vue/observer'
const internalRE = /^_|^\$/ const internalRE = /^_|^\$/
export function initializeState(instance: ComponentInstance) { export function initializeState(instance: ComponentInstance) {
if (instance.data) { const { data } = instance.$options
instance._rawData = instance.data() const rawData = (instance._rawData = (data ? data.call(instance) : {}) as any)
} else { const keys = Object.keys(instance)
const keys = Object.keys(instance) for (let i = 0; i < keys.length; i++) {
const data = (instance._rawData = {} as any) const key = keys[i]
for (let i = 0; i < keys.length; i++) { if (!internalRE.test(key)) {
const key = keys[i] rawData[key] = (instance as any)[key]
if (!internalRE.test(key)) {
data[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 currentVNode = vnode
currentContextVNode = vnode.contextVNode currentContextVNode = vnode.contextVNode
const instance = (vnode.children = new Component() as ComponentInstance) const instance = (vnode.children = new Component() as ComponentInstance)
// then we finish the initialization by collecting properties set on the // then we finish the initialization by collecting properties set on the
// instance // instance
const {
$proxy,
$options: { created, computed, watch }
} = instance
initializeState(instance) initializeState(instance)
initializeComputed(instance, instance.$options.computed) initializeComputed(instance, computed)
initializeWatch(instance, instance.$options.watch) initializeWatch(instance, watch)
instance.$slots = currentVNode.slots || EMPTY_OBJ instance.$slots = currentVNode.slots || EMPTY_OBJ
if (instance.created) {
instance.created.call(instance.$proxy) if (created) {
created.call($proxy)
} }
currentVNode = currentContextVNode = null currentVNode = currentContextVNode = null
return instance return instance
} }
@ -87,14 +94,11 @@ export function initializeComponentInstance(instance: ComponentInstance) {
} }
// beforeCreate hook is called right in the constructor // beforeCreate hook is called right in the constructor
if (instance.beforeCreate) { const { beforeCreate, props } = instance.$options
instance.beforeCreate.call(proxy) if (beforeCreate) {
beforeCreate.call(proxy)
} }
initializeProps( initializeProps(instance, props, (currentVNode as VNode).data)
instance,
instance.$options.props,
(currentVNode as VNode).data
)
} }
export function renderInstanceRoot(instance: ComponentInstance): VNode { export function renderInstanceRoot(instance: ComponentInstance): VNode {

View File

@ -1167,8 +1167,13 @@ export function createRenderer(options: RendererOptions) {
;(instance as any).$unmount = unmountComponentInstance ;(instance as any).$unmount = unmountComponentInstance
} }
if (instance.beforeMount) { const {
instance.beforeMount.call(instance.$proxy) $proxy,
$options: { beforeMount, mounted, renderTracked, renderTriggered }
} = instance
if (beforeMount) {
beforeMount.call($proxy)
} }
const queueUpdate = (instance.$forceUpdate = () => { const queueUpdate = (instance.$forceUpdate = () => {
@ -1200,33 +1205,26 @@ export function createRenderer(options: RendererOptions) {
} }
instance._mounted = true instance._mounted = true
mountComponentInstanceCallbacks(instance, vnode.ref) if (vnode.ref) {
mountRef(vnode.ref, $proxy)
}
if (mounted) {
lifecycleHooks.push(() => {
mounted.call($proxy)
})
}
} }
}, },
{ {
scheduler: queueUpdate, scheduler: queueUpdate,
onTrack: instance.renderTracked, onTrack: renderTracked,
onTrigger: instance.renderTriggered onTrigger: renderTriggered
} }
) )
return vnode.el as RenderNode 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( function updateComponentInstance(
instance: ComponentInstance, instance: ComponentInstance,
isSVG: boolean isSVG: boolean
@ -1234,23 +1232,22 @@ export function createRenderer(options: RendererOptions) {
if (__DEV__ && instance.$parentVNode) { if (__DEV__ && instance.$parentVNode) {
pushWarningContext(instance.$parentVNode as VNode) pushWarningContext(instance.$parentVNode as VNode)
} }
const prevVNode = instance.$vnode
if (instance.beforeUpdate) { const {
instance.beforeUpdate.call(instance.$proxy, prevVNode) $vnode: prevVNode,
$parentVNode,
$proxy,
$options: { beforeUpdate, updated }
} = instance
if (beforeUpdate) {
beforeUpdate.call($proxy, prevVNode)
} }
const nextVNode = (instance.$vnode = renderInstanceRoot( const nextVNode = (instance.$vnode = renderInstanceRoot(
instance instance
) as MountedVNode) ) as MountedVNode)
const container = platformParentNode(prevVNode.el) as RenderNode const container = platformParentNode(prevVNode.el) as RenderNode
patch( patch(prevVNode, nextVNode, container, $parentVNode as MountedVNode, isSVG)
prevVNode,
nextVNode,
container,
instance.$parentVNode as MountedVNode,
isSVG
)
const el = nextVNode.el as RenderNode const el = nextVNode.el as RenderNode
if (__COMPAT__) { if (__COMPAT__) {
@ -1260,7 +1257,7 @@ export function createRenderer(options: RendererOptions) {
// recursively update contextVNode el for nested HOCs // recursively update contextVNode el for nested HOCs
if ((nextVNode.flags & VNodeFlags.PORTAL) === 0) { if ((nextVNode.flags & VNodeFlags.PORTAL) === 0) {
let vnode = instance.$parentVNode let vnode = $parentVNode
while (vnode !== null) { while (vnode !== null) {
if ((vnode.flags & VNodeFlags.COMPONENT) > 0) { if ((vnode.flags & VNodeFlags.COMPONENT) > 0) {
vnode.el = el 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 // Because the child's update is executed by the scheduler and not
// synchronously within the parent's update call, the child's updated hook // 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 // 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 // invoked BEFORE the parent's. Therefore we add them to the head of the
// queue instead. // queue instead.
lifecycleHooks.unshift(() => { 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) { if (instance._unmounted) {
return return
} }
if (instance.beforeUnmount) { const {
instance.beforeUnmount.call(instance.$proxy) $vnode,
$proxy,
_updateHandle,
$options: { beforeUnmount, unmounted }
} = instance
if (beforeUnmount) {
beforeUnmount.call($proxy)
} }
if (instance.$vnode) { if ($vnode) {
unmount(instance.$vnode) unmount($vnode)
} }
stop(instance._updateHandle) stop(_updateHandle)
teardownComponentInstance(instance) teardownComponentInstance(instance)
instance._unmounted = true instance._unmounted = true
if (instance.unmounted) { if (unmounted) {
instance.unmounted.call(instance.$proxy) unmounted.call($proxy)
} }
} }
@ -1338,12 +1341,16 @@ export function createRenderer(options: RendererOptions) {
} }
if (asRoot || !instance._inactiveRoot) { if (asRoot || !instance._inactiveRoot) {
// 2. recursively call activated on child tree, depth-first // 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++) { for (let i = 0; i < $children.length; i++) {
callActivatedHook($children[i], false) callActivatedHook($children[i], false)
} }
if (instance.activated) { if (activated) {
instance.activated.call(instance.$proxy) activated.call($proxy)
} }
} }
} }
@ -1359,12 +1366,16 @@ export function createRenderer(options: RendererOptions) {
} }
if (asRoot || !instance._inactiveRoot) { if (asRoot || !instance._inactiveRoot) {
// 2. recursively call deactivated on child tree, depth-first // 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++) { for (let i = 0; i < $children.length; i++) {
callDeactivateHook($children[i], false) callDeactivateHook($children[i], false)
} }
if (instance.deactivated) { if (deactivated) {
instance.deactivated.call(instance.$proxy) deactivated.call($proxy)
} }
} }
} }

View File

@ -99,7 +99,7 @@ interface createElement extends VNodeFactories {
} }
export const h = ((tag: ElementType, data?: any, children?: any): VNode => { 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 children = data
data = null data = null
} }