From 5cd1f33de3889cf7d785f38bf44d31d042ddddfa Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 10 Oct 2018 13:13:27 -0400 Subject: [PATCH] wip: refactor parentChain management --- packages/core/__tests__/parentChain.spec.ts | 122 +++++++++++++ packages/core/src/componentUtils.ts | 14 +- packages/core/src/createRenderer.ts | 192 ++++++++++---------- packages/core/src/index.ts | 7 +- packages/core/src/vdom.ts | 6 +- packages/renderer-dom/src/index.ts | 7 +- packages/renderer-test/src/index.ts | 7 +- 7 files changed, 251 insertions(+), 104 deletions(-) create mode 100644 packages/core/__tests__/parentChain.spec.ts diff --git a/packages/core/__tests__/parentChain.spec.ts b/packages/core/__tests__/parentChain.spec.ts new file mode 100644 index 00000000..93dd7528 --- /dev/null +++ b/packages/core/__tests__/parentChain.spec.ts @@ -0,0 +1,122 @@ +import { + h, + Component, + render, + nodeOps, + ComponentInstance, + observable, + nextTick +} from '@vue/renderer-test' + +describe('Parent chain management', () => { + it('should have correct $parent / $root / $children', async () => { + let child: any + let grandChildren: any[] = [] + + const state = observable({ ok: true }) + + class Parent extends Component { + render() { + return h(Child) + } + } + + class Child extends Component { + created() { + child = this + } + render() { + return [state.ok ? h(GrandChild) : null, h(GrandChild)] + } + } + + class GrandChild extends Component { + created() { + grandChildren.push(this) + } + unmounted() { + grandChildren.splice(grandChildren.indexOf(this), 1) + } + render() {} + } + + const root = nodeOps.createElement('div') + const parent = render(h(Parent), root) as ComponentInstance + + expect(child.$parent).toBe(parent) + expect(child.$root).toBe(parent) + + grandChildren.forEach(grandChild => { + expect(grandChild.$parent).toBe(child) + expect(grandChild.$root).toBe(parent) + }) + + expect(parent.$children).toEqual([child]) + expect(grandChildren.length).toBe(2) + expect(child.$children).toEqual(grandChildren) + + state.ok = false + await nextTick() + expect(grandChildren.length).toBe(1) + expect(child.$children).toEqual(grandChildren) + }) + + it('should have correct $parent / $root w/ functional component in between', async () => { + let child: any + let grandChildren: any[] = [] + + const state = observable({ ok: true }) + + class Parent extends Component { + render() { + return h(FunctionalChild) + } + } + + const FunctionalChild = () => h(Child) + + class Child extends Component { + created() { + child = this + } + render() { + return [ + state.ok ? h(FunctionalGrandChild) : null, + h(FunctionalGrandChild) + ] + } + } + + const FunctionalGrandChild = () => h(GrandChild) + + class GrandChild extends Component { + created() { + grandChildren.push(this) + } + unmounted() { + grandChildren.splice(grandChildren.indexOf(this), 1) + } + render() {} + } + + const root = nodeOps.createElement('div') + const parent = render(h(Parent), root) as ComponentInstance + + expect(child.$parent).toBe(parent) + expect(child.$root).toBe(parent) + + grandChildren.forEach(grandChild => { + expect(grandChild.$parent).toBe(child) + expect(grandChild.$root).toBe(parent) + }) + + expect(parent.$children).toEqual([child]) + expect(grandChildren.length).toBe(2) + expect(child.$children).toEqual(grandChildren) + + state.ok = false + await nextTick() + expect(grandChildren.length).toBe(1) + expect(child.$children).toEqual(grandChildren) + }) +}) diff --git a/packages/core/src/componentUtils.ts b/packages/core/src/componentUtils.ts index cd58957c..8f9ab8b6 100644 --- a/packages/core/src/componentUtils.ts +++ b/packages/core/src/componentUtils.ts @@ -19,7 +19,7 @@ import { handleError, ErrorTypes } from './errorHandling' export function createComponentInstance( vnode: VNode, Component: ComponentClass, - parentComponent: ComponentInstance | null + contextVNode: MountedVNode | null ): ComponentInstance { const instance = (vnode.children = new Component()) as ComponentInstance instance.$parentVNode = vnode as MountedVNode @@ -28,7 +28,17 @@ export function createComponentInstance( const proxy = (instance.$proxy = createRenderProxy(instance)) // pointer management - if (parentComponent) { + if (contextVNode !== null) { + // locate first non-functional parent + while ( + contextVNode !== null && + contextVNode.flags & VNodeFlags.COMPONENT_FUNCTIONAL && + contextVNode.contextVNode !== null + ) { + contextVNode = contextVNode.contextVNode as any + } + const parentComponent = (contextVNode as VNode) + .children as ComponentInstance instance.$parent = parentComponent.$proxy instance.$root = parentComponent.$root parentComponent.$children.push(proxy) diff --git a/packages/core/src/createRenderer.ts b/packages/core/src/createRenderer.ts index cf55e002..5cb35e9b 100644 --- a/packages/core/src/createRenderer.ts +++ b/packages/core/src/createRenderer.ts @@ -111,36 +111,30 @@ export function createRenderer(options: RendererOptions) { function mount( vnode: VNode, container: RenderNode | null, - parentComponent: ComponentInstance | null, + contextVNode: MountedVNode | null, isSVG: boolean, endNode: RenderNode | null ) { const { flags } = vnode if (flags & VNodeFlags.ELEMENT) { - mountElement(vnode, container, parentComponent, isSVG, endNode) + mountElement(vnode, container, contextVNode, isSVG, endNode) } else if (flags & VNodeFlags.COMPONENT_STATEFUL) { - mountStatefulComponent(vnode, container, parentComponent, isSVG, endNode) + mountStatefulComponent(vnode, container, contextVNode, isSVG, endNode) } else if (flags & VNodeFlags.COMPONENT_FUNCTIONAL) { - mountFunctionalComponent( - vnode, - container, - parentComponent, - isSVG, - endNode - ) + mountFunctionalComponent(vnode, container, contextVNode, isSVG, endNode) } else if (flags & VNodeFlags.TEXT) { mountText(vnode, container, endNode) } else if (flags & VNodeFlags.FRAGMENT) { - mountFragment(vnode, container, parentComponent, isSVG, endNode) + mountFragment(vnode, container, contextVNode, isSVG, endNode) } else if (flags & VNodeFlags.PORTAL) { - mountPortal(vnode, container, parentComponent) + mountPortal(vnode, container, contextVNode) } } function mountArrayChildren( children: VNode[], container: RenderNode | null, - parentComponent: ComponentInstance | null, + contextVNode: MountedVNode | null, isSVG: boolean, endNode: RenderNode | null ) { @@ -149,14 +143,14 @@ export function createRenderer(options: RendererOptions) { if (child.el) { children[i] = child = cloneVNode(child) } - mount(children[i], container, parentComponent, isSVG, endNode) + mount(children[i], container, contextVNode, isSVG, endNode) } } function mountElement( vnode: VNode, container: RenderNode | null, - parentComponent: ComponentInstance | null, + contextVNode: MountedVNode | null, isSVG: boolean, endNode: RenderNode | null ) { @@ -174,12 +168,12 @@ export function createRenderer(options: RendererOptions) { if (childFlags !== ChildrenFlags.NO_CHILDREN) { const hasSVGChildren = isSVG && tag !== 'foreignObject' if (childFlags & ChildrenFlags.SINGLE_VNODE) { - mount(children as VNode, el, parentComponent, hasSVGChildren, endNode) + mount(children as VNode, el, contextVNode, hasSVGChildren, endNode) } else if (childFlags & ChildrenFlags.MULTIPLE_VNODES) { mountArrayChildren( children as VNode[], el, - parentComponent, + contextVNode, hasSVGChildren, endNode ) @@ -207,10 +201,11 @@ export function createRenderer(options: RendererOptions) { function mountStatefulComponent( vnode: VNode, container: RenderNode | null, - parentComponent: ComponentInstance | null, + contextVNode: MountedVNode | null, isSVG: boolean, endNode: RenderNode | null ) { + vnode.contextVNode = contextVNode if (vnode.flags & VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE) { // kept-alive activateComponentInstance(vnode) @@ -219,7 +214,7 @@ export function createRenderer(options: RendererOptions) { vnode, vnode.tag as ComponentClass, container, - parentComponent, + contextVNode, isSVG, endNode ) @@ -229,10 +224,11 @@ export function createRenderer(options: RendererOptions) { function mountFunctionalComponent( vnode: VNode, container: RenderNode | null, - parentComponent: ComponentInstance | null, + contextVNode: MountedVNode | null, isSVG: boolean, endNode: RenderNode | null ) { + vnode.contextVNode = contextVNode const { tag, data, slots } = vnode const render = tag as FunctionalComponent const { props, attrs } = resolveProps(data, render.props) @@ -240,7 +236,7 @@ export function createRenderer(options: RendererOptions) { render(props, slots || EMPTY_OBJ, attrs || EMPTY_OBJ), vnode )) - mount(subTree, container, parentComponent, isSVG, endNode) + mount(subTree, container, vnode as MountedVNode, isSVG, endNode) vnode.el = subTree.el as RenderNode } @@ -258,14 +254,14 @@ export function createRenderer(options: RendererOptions) { function mountFragment( vnode: VNode, container: RenderNode | null, - parentComponent: ComponentInstance | null, + contextVNode: MountedVNode | null, isSVG: boolean, endNode: RenderNode | null ) { const { children, childFlags } = vnode switch (childFlags) { case ChildrenFlags.SINGLE_VNODE: - mount(children as VNode, container, parentComponent, isSVG, endNode) + mount(children as VNode, container, contextVNode, isSVG, endNode) vnode.el = (children as MountedVNode).el break case ChildrenFlags.NO_CHILDREN: @@ -277,7 +273,7 @@ export function createRenderer(options: RendererOptions) { mountArrayChildren( children as VNode[], container, - parentComponent, + contextVNode, isSVG, endNode ) @@ -288,7 +284,7 @@ export function createRenderer(options: RendererOptions) { function mountPortal( vnode: VNode, container: RenderNode | null, - parentComponent: ComponentInstance | null + contextVNode: MountedVNode | null ) { const { tag, children, childFlags, ref } = vnode const target = typeof tag === 'string' ? platformQuerySelector(tag) : tag @@ -298,18 +294,12 @@ export function createRenderer(options: RendererOptions) { } if (childFlags & ChildrenFlags.SINGLE_VNODE) { - mount( - children as VNode, - target as RenderNode, - parentComponent, - false, - null - ) + mount(children as VNode, target as RenderNode, contextVNode, false, null) } else if (childFlags & ChildrenFlags.MULTIPLE_VNODES) { mountArrayChildren( children as VNode[], target as RenderNode, - parentComponent, + contextVNode, false, null ) @@ -352,24 +342,24 @@ export function createRenderer(options: RendererOptions) { prevVNode: MountedVNode, nextVNode: VNode, container: RenderNode, - parentComponent: ComponentInstance | null, + contextVNode: MountedVNode | null, isSVG: boolean ) { const nextFlags = nextVNode.flags const prevFlags = prevVNode.flags if (prevFlags !== nextFlags) { - replaceVNode(prevVNode, nextVNode, container, parentComponent, isSVG) + replaceVNode(prevVNode, nextVNode, container, contextVNode, isSVG) } else if (nextFlags & VNodeFlags.ELEMENT) { - patchElement(prevVNode, nextVNode, container, parentComponent, isSVG) + patchElement(prevVNode, nextVNode, container, contextVNode, isSVG) } else if (nextFlags & VNodeFlags.COMPONENT) { - patchComponent(prevVNode, nextVNode, container, parentComponent, isSVG) + patchComponent(prevVNode, nextVNode, container, contextVNode, isSVG) } else if (nextFlags & VNodeFlags.TEXT) { patchText(prevVNode, nextVNode) } else if (nextFlags & VNodeFlags.FRAGMENT) { - patchFragment(prevVNode, nextVNode, container, parentComponent, isSVG) + patchFragment(prevVNode, nextVNode, container, contextVNode, isSVG) } else if (nextFlags & VNodeFlags.PORTAL) { - patchPortal(prevVNode, nextVNode, parentComponent) + patchPortal(prevVNode, nextVNode, contextVNode) } } @@ -377,14 +367,14 @@ export function createRenderer(options: RendererOptions) { prevVNode: MountedVNode, nextVNode: VNode, container: RenderNode, - parentComponent: ComponentInstance | null, + contextVNode: MountedVNode | null, isSVG: boolean ) { const { flags, tag } = nextVNode isSVG = isSVG || (flags & VNodeFlags.ELEMENT_SVG) > 0 if (prevVNode.tag !== tag) { - replaceVNode(prevVNode, nextVNode, container, parentComponent, isSVG) + replaceVNode(prevVNode, nextVNode, container, contextVNode, isSVG) return } @@ -434,7 +424,7 @@ export function createRenderer(options: RendererOptions) { prevVNode.children, nextVNode.children, el, - parentComponent, + contextVNode, isSVG && nextVNode.tag !== 'foreignObject', null ) @@ -450,12 +440,13 @@ export function createRenderer(options: RendererOptions) { prevVNode: MountedVNode, nextVNode: VNode, container: RenderNode, - parentComponent: ComponentInstance | null, + contextVNode: MountedVNode | null, isSVG: boolean ) { + nextVNode.contextVNode = contextVNode const { tag, flags } = nextVNode if (tag !== prevVNode.tag) { - replaceVNode(prevVNode, nextVNode, container, parentComponent, isSVG) + replaceVNode(prevVNode, nextVNode, container, contextVNode, isSVG) } else if (flags & VNodeFlags.COMPONENT_STATEFUL) { patchStatefulComponent(prevVNode, nextVNode) } else { @@ -463,7 +454,7 @@ export function createRenderer(options: RendererOptions) { prevVNode, nextVNode, container, - parentComponent, + contextVNode, isSVG ) } @@ -497,7 +488,7 @@ export function createRenderer(options: RendererOptions) { if (shouldForceUpdate) { instance.$forceUpdate() } else if (instance.$vnode.flags & VNodeFlags.COMPONENT) { - instance.$vnode.parentVNode = nextVNode + instance.$vnode.contextVNode = nextVNode } nextVNode.el = instance.$vnode.el } @@ -506,7 +497,7 @@ export function createRenderer(options: RendererOptions) { prevVNode: MountedVNode, nextVNode: VNode, container: RenderNode, - parentComponent: ComponentInstance | null, + contextVNode: MountedVNode | null, isSVG: boolean ) { // functional component tree is stored on the vnode as `children` @@ -526,11 +517,11 @@ export function createRenderer(options: RendererOptions) { render(props, nextSlots || EMPTY_OBJ, attrs || EMPTY_OBJ), nextVNode )) - patch(prevTree, nextTree, container, parentComponent, isSVG) + patch(prevTree, nextTree, container, nextVNode as MountedVNode, isSVG) nextVNode.el = nextTree.el } else if (prevTree.flags & VNodeFlags.COMPONENT) { // functional component returned another component - prevTree.parentVNode = nextVNode + prevTree.contextVNode = nextVNode } } @@ -538,7 +529,7 @@ export function createRenderer(options: RendererOptions) { prevVNode: MountedVNode, nextVNode: VNode, container: RenderNode, - parentComponent: ComponentInstance | null, + contextVNode: MountedVNode | null, isSVG: boolean ) { // determine the tail node of the previous fragment, @@ -551,7 +542,7 @@ export function createRenderer(options: RendererOptions) { prevVNode.children, children, container, - parentComponent, + contextVNode, isSVG, endNode ) @@ -595,7 +586,7 @@ export function createRenderer(options: RendererOptions) { function patchPortal( prevVNode: MountedVNode, nextVNode: VNode, - parentComponent: ComponentInstance | null + contextVNode: MountedVNode | null ) { const prevContainer = prevVNode.tag as RenderNode const nextContainer = nextVNode.tag as RenderNode @@ -606,7 +597,7 @@ export function createRenderer(options: RendererOptions) { prevVNode.children, nextChildren, prevContainer, - parentComponent, + contextVNode, false, null ) @@ -631,12 +622,12 @@ export function createRenderer(options: RendererOptions) { prevVNode: MountedVNode, nextVNode: VNode, container: RenderNode, - parentComponent: ComponentInstance | null, + contextVNode: MountedVNode | null, isSVG: boolean ) { const refNode = platformNextSibling(getVNodeLastEl(prevVNode)) removeVNode(prevVNode, container) - mount(nextVNode, container, parentComponent, isSVG, refNode) + mount(nextVNode, container, contextVNode, isSVG, refNode) } function patchChildren( @@ -645,7 +636,7 @@ export function createRenderer(options: RendererOptions) { prevChildren: VNodeChildren, nextChildren: VNodeChildren, container: RenderNode, - parentComponent: ComponentInstance | null, + contextVNode: MountedVNode | null, isSVG: boolean, endNode: RenderNode | null ) { @@ -657,7 +648,7 @@ export function createRenderer(options: RendererOptions) { prevChildren as MountedVNode, nextChildren as VNode, container, - parentComponent, + contextVNode, isSVG ) break @@ -669,7 +660,7 @@ export function createRenderer(options: RendererOptions) { mountArrayChildren( nextChildren as VNode[], container, - parentComponent, + contextVNode, isSVG, endNode ) @@ -682,7 +673,7 @@ export function createRenderer(options: RendererOptions) { mount( nextChildren as VNode, container, - parentComponent, + contextVNode, isSVG, endNode ) @@ -693,7 +684,7 @@ export function createRenderer(options: RendererOptions) { mountArrayChildren( nextChildren as VNode[], container, - parentComponent, + contextVNode, isSVG, endNode ) @@ -704,13 +695,7 @@ export function createRenderer(options: RendererOptions) { // MULTIPLE_CHILDREN if (nextChildFlags === ChildrenFlags.SINGLE_VNODE) { removeChildren(prevChildren as MountedVNode[], container, endNode) - mount( - nextChildren as VNode, - container, - parentComponent, - isSVG, - endNode - ) + mount(nextChildren as VNode, container, contextVNode, isSVG, endNode) } else if (nextChildFlags === ChildrenFlags.NO_CHILDREN) { removeChildren(prevChildren as MountedVNode[], container, endNode) } else { @@ -721,7 +706,7 @@ export function createRenderer(options: RendererOptions) { mountArrayChildren( nextChildren as VNode[], container, - parentComponent, + contextVNode, isSVG, endNode ) @@ -738,7 +723,7 @@ export function createRenderer(options: RendererOptions) { container, prevLength, nextLength, - parentComponent, + contextVNode, isSVG, endNode ) @@ -749,7 +734,7 @@ export function createRenderer(options: RendererOptions) { container, prevLength, nextLength, - parentComponent, + contextVNode, isSVG, endNode ) @@ -765,7 +750,7 @@ export function createRenderer(options: RendererOptions) { container: RenderNode, prevLength: number, nextLength: number, - parentComponent: ComponentInstance | null, + contextVNode: MountedVNode | null, isSVG: boolean, endNode: RenderNode | null ) { @@ -779,7 +764,7 @@ export function createRenderer(options: RendererOptions) { if (nextChild.el) { nextChildren[i] = nextChild = cloneVNode(nextChild) } - patch(prevChild, nextChild, container, parentComponent, isSVG) + patch(prevChild, nextChild, container, contextVNode, isSVG) prevChildren[i] = nextChild as MountedVNode } if (prevLength < nextLength) { @@ -788,7 +773,7 @@ export function createRenderer(options: RendererOptions) { if (nextChild.el) { nextChildren[i] = nextChild = cloneVNode(nextChild) } - mount(nextChild, container, parentComponent, isSVG, endNode) + mount(nextChild, container, contextVNode, isSVG, endNode) } } else if (prevLength > nextLength) { for (i = commonLength; i < prevLength; i++) { @@ -803,7 +788,7 @@ export function createRenderer(options: RendererOptions) { container: RenderNode, prevLength: number, nextLength: number, - parentComponent: ComponentInstance | null, + contextVNode: MountedVNode | null, isSVG: boolean, endNode: RenderNode | null ) { @@ -821,7 +806,7 @@ export function createRenderer(options: RendererOptions) { if (nextVNode.el) { nextChildren[j] = nextVNode = cloneVNode(nextVNode) } - patch(prevVNode, nextVNode, container, parentComponent, isSVG) + patch(prevVNode, nextVNode, container, contextVNode, isSVG) prevChildren[j] = nextVNode as MountedVNode j++ if (j > prevEnd || j > nextEnd) { @@ -839,7 +824,7 @@ export function createRenderer(options: RendererOptions) { if (nextVNode.el) { nextChildren[nextEnd] = nextVNode = cloneVNode(nextVNode) } - patch(prevVNode, nextVNode, container, parentComponent, isSVG) + patch(prevVNode, nextVNode, container, contextVNode, isSVG) prevChildren[prevEnd] = nextVNode as MountedVNode prevEnd-- nextEnd-- @@ -862,7 +847,7 @@ export function createRenderer(options: RendererOptions) { nextChildren[j] = nextVNode = cloneVNode(nextVNode) } j++ - mount(nextVNode, container, parentComponent, isSVG, nextNode) + mount(nextVNode, container, contextVNode, isSVG, nextNode) } } } else if (j > nextEnd) { @@ -907,7 +892,7 @@ export function createRenderer(options: RendererOptions) { if (nextVNode.el) { nextChildren[j] = nextVNode = cloneVNode(nextVNode) } - patch(prevVNode, nextVNode, container, parentComponent, isSVG) + patch(prevVNode, nextVNode, container, contextVNode, isSVG) patched++ break } @@ -951,7 +936,7 @@ export function createRenderer(options: RendererOptions) { if (nextVNode.el) { nextChildren[j] = nextVNode = cloneVNode(nextVNode) } - patch(prevVNode, nextVNode, container, parentComponent, isSVG) + patch(prevVNode, nextVNode, container, contextVNode, isSVG) patched++ } else if (!canRemoveWholeContent) { removeVNode(prevVNode, container) @@ -967,7 +952,7 @@ export function createRenderer(options: RendererOptions) { mountArrayChildren( nextChildren, container, - parentComponent, + contextVNode, isSVG, endNode ) @@ -986,7 +971,7 @@ export function createRenderer(options: RendererOptions) { mount( nextVNode, container, - parentComponent, + contextVNode, isSVG, nextPos < nextLength ? nextChildren[nextPos].el : endNode ) @@ -1017,7 +1002,7 @@ export function createRenderer(options: RendererOptions) { mount( nextVNode, container, - parentComponent, + contextVNode, isSVG, nextPos < nextLength ? nextChildren[nextPos].el : endNode ) @@ -1149,18 +1134,18 @@ export function createRenderer(options: RendererOptions) { // Component lifecycle ------------------------------------------------------- function mountComponentInstance( - parentVNode: VNode, + vnode: VNode, Component: ComponentClass, container: RenderNode | null, - parentComponent: ComponentInstance | null, + contextVNode: MountedVNode | null, isSVG: boolean, endNode: RenderNode | null ): RenderNode { // a vnode may already have an instance if this is a compat call with // new Vue() const instance = - (__COMPAT__ && (parentVNode.children as ComponentInstance)) || - createComponentInstance(parentVNode, Component, parentComponent) + (__COMPAT__ && (vnode.children as ComponentInstance)) || + createComponentInstance(vnode, Component, contextVNode) // inject platform-specific unmount to keep-alive container if ((Component as any)[KeepAliveSymbol] === true) { @@ -1185,16 +1170,22 @@ export function createRenderer(options: RendererOptions) { } else { // this will be executed synchronously right here instance.$vnode = renderInstanceRoot(instance) as MountedVNode - mount(instance.$vnode, container, instance, isSVG, endNode) - parentVNode.el = instance.$vnode.el + mount( + instance.$vnode, + container, + vnode as MountedVNode, + isSVG, + endNode + ) + vnode.el = instance.$vnode.el if (__DEV__) { // expose __vue__ for devtools - ;(parentVNode.el as any).__vue__ = instance + ;(vnode.el as any).__vue__ = instance } instance._mounted = true - mountComponentInstanceCallbacks(instance, parentVNode.ref) + mountComponentInstanceCallbacks(instance, vnode.ref) } }, { @@ -1204,7 +1195,7 @@ export function createRenderer(options: RendererOptions) { } ) - return parentVNode.el as RenderNode + return vnode.el as RenderNode } function mountComponentInstanceCallbacks( @@ -1212,7 +1203,7 @@ export function createRenderer(options: RendererOptions) { ref: Ref | null ) { if (ref) { - mountRef(ref, instance) + mountRef(ref, instance.$proxy) } if (instance.mounted) { lifecycleHooks.push(() => { @@ -1235,7 +1226,13 @@ export function createRenderer(options: RendererOptions) { instance ) as MountedVNode) const container = platformParentNode(prevVNode.el) as RenderNode - patch(prevVNode, nextVNode, container, instance, isSVG) + patch( + prevVNode, + nextVNode, + container, + instance.$parentVNode as MountedVNode, + isSVG + ) const el = nextVNode.el as RenderNode if (__DEV__) { @@ -1243,14 +1240,14 @@ export function createRenderer(options: RendererOptions) { ;(el as any).__vue__ = instance } - // recursively update parentVNode el for nested HOCs + // recursively update contextVNode el for nested HOCs if ((nextVNode.flags & VNodeFlags.PORTAL) === 0) { let vnode = instance.$parentVNode while (vnode !== null) { if ((vnode.flags & VNodeFlags.COMPONENT) > 0) { vnode.el = el } - vnode = vnode.parentVNode + vnode = vnode.contextVNode } } @@ -1354,7 +1351,10 @@ export function createRenderer(options: RendererOptions) { // API ----------------------------------------------------------------------- - function render(vnode: VNode | null, container: any) { + function render( + vnode: VNode | null, + container: any + ): ComponentInstance | null { const prevVNode = container.vnode if (vnode && vnode.el) { vnode = cloneVNode(vnode) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 6c2d323c..2feec952 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -24,7 +24,12 @@ export { createAsyncComponent } from './optional/asyncComponent' export { KeepAlive } from './optional/keepAlive' // flags & types -export { ComponentType, ComponentClass, FunctionalComponent } from './component' +export { + ComponentType, + ComponentClass, + FunctionalComponent, + ComponentInstance +} from './component' export * from './componentOptions' export { VNodeFlags, ChildrenFlags } from './flags' export { VNode, Slots } from './vdom' diff --git a/packages/core/src/vdom.ts b/packages/core/src/vdom.ts index 0719bdfb..0b1d948a 100644 --- a/packages/core/src/vdom.ts +++ b/packages/core/src/vdom.ts @@ -31,6 +31,9 @@ export interface VNode { // points to parent component's placeholder vnode // this is used to update vnode.el for nested HOCs. parentVNode: VNode | null + // only on mounted component nodes + // points to the parent stateful/functional component's placeholder node + contextVNode: VNode | null } export interface MountedVNode extends VNode { @@ -84,7 +87,8 @@ export function createVNode( ref: ref === void 0 ? null : ref, slots: slots === void 0 ? null : slots, el: null, - parentVNode: null + parentVNode: null, + contextVNode: null } if (childFlags === ChildrenFlags.UNKNOWN_CHILDREN) { normalizeChildren(vnode, children) diff --git a/packages/renderer-dom/src/index.ts b/packages/renderer-dom/src/index.ts index 7d5bd465..b9162429 100644 --- a/packages/renderer-dom/src/index.ts +++ b/packages/renderer-dom/src/index.ts @@ -1,4 +1,4 @@ -import { createRenderer, VNode } from '@vue/core' +import { createRenderer, VNode, ComponentInstance } from '@vue/core' import { nodeOps } from './nodeOps' import { patchData } from './patchData' import { teardownVNode } from './teardownVNode' @@ -9,7 +9,10 @@ const { render: _render } = createRenderer({ teardownVNode }) -type publicRender = (node: VNode | null, container: HTMLElement) => void +type publicRender = ( + node: VNode | null, + container: HTMLElement +) => ComponentInstance | null export const render = _render as publicRender // re-export everything from core diff --git a/packages/renderer-test/src/index.ts b/packages/renderer-test/src/index.ts index 9a7d0322..ba406389 100644 --- a/packages/renderer-test/src/index.ts +++ b/packages/renderer-test/src/index.ts @@ -1,4 +1,4 @@ -import { createRenderer, VNode } from '@vue/core' +import { createRenderer, VNode, ComponentInstance } from '@vue/core' import { nodeOps, TestElement } from './nodeOps' import { patchData } from './patchData' @@ -7,7 +7,10 @@ const { render: _render } = createRenderer({ patchData }) -type publicRender = (node: VNode | null, container: TestElement) => void +type publicRender = ( + node: VNode | null, + container: TestElement +) => ComponentInstance | null export const render = _render as publicRender export { serialize } from './serialize'