wip: refactor parentChain management

This commit is contained in:
Evan You 2018-10-10 13:13:27 -04:00
parent ef0c6effe8
commit 5cd1f33de3
7 changed files with 251 additions and 104 deletions

View File

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

View File

@ -19,7 +19,7 @@ import { handleError, ErrorTypes } from './errorHandling'
export function createComponentInstance( export function createComponentInstance(
vnode: VNode, vnode: VNode,
Component: ComponentClass, Component: ComponentClass,
parentComponent: ComponentInstance | null contextVNode: MountedVNode | null
): ComponentInstance { ): ComponentInstance {
const instance = (vnode.children = new Component()) as ComponentInstance const instance = (vnode.children = new Component()) as ComponentInstance
instance.$parentVNode = vnode as MountedVNode instance.$parentVNode = vnode as MountedVNode
@ -28,7 +28,17 @@ export function createComponentInstance(
const proxy = (instance.$proxy = createRenderProxy(instance)) const proxy = (instance.$proxy = createRenderProxy(instance))
// pointer management // 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.$parent = parentComponent.$proxy
instance.$root = parentComponent.$root instance.$root = parentComponent.$root
parentComponent.$children.push(proxy) parentComponent.$children.push(proxy)

View File

@ -111,36 +111,30 @@ export function createRenderer(options: RendererOptions) {
function mount( function mount(
vnode: VNode, vnode: VNode,
container: RenderNode | null, container: RenderNode | null,
parentComponent: ComponentInstance | null, contextVNode: MountedVNode | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | null endNode: RenderNode | null
) { ) {
const { flags } = vnode const { flags } = vnode
if (flags & VNodeFlags.ELEMENT) { if (flags & VNodeFlags.ELEMENT) {
mountElement(vnode, container, parentComponent, isSVG, endNode) mountElement(vnode, container, contextVNode, isSVG, endNode)
} else if (flags & VNodeFlags.COMPONENT_STATEFUL) { } else if (flags & VNodeFlags.COMPONENT_STATEFUL) {
mountStatefulComponent(vnode, container, parentComponent, isSVG, endNode) mountStatefulComponent(vnode, container, contextVNode, isSVG, endNode)
} else if (flags & VNodeFlags.COMPONENT_FUNCTIONAL) { } else if (flags & VNodeFlags.COMPONENT_FUNCTIONAL) {
mountFunctionalComponent( mountFunctionalComponent(vnode, container, contextVNode, isSVG, endNode)
vnode,
container,
parentComponent,
isSVG,
endNode
)
} else if (flags & VNodeFlags.TEXT) { } else if (flags & VNodeFlags.TEXT) {
mountText(vnode, container, endNode) mountText(vnode, container, endNode)
} else if (flags & VNodeFlags.FRAGMENT) { } else if (flags & VNodeFlags.FRAGMENT) {
mountFragment(vnode, container, parentComponent, isSVG, endNode) mountFragment(vnode, container, contextVNode, isSVG, endNode)
} else if (flags & VNodeFlags.PORTAL) { } else if (flags & VNodeFlags.PORTAL) {
mountPortal(vnode, container, parentComponent) mountPortal(vnode, container, contextVNode)
} }
} }
function mountArrayChildren( function mountArrayChildren(
children: VNode[], children: VNode[],
container: RenderNode | null, container: RenderNode | null,
parentComponent: ComponentInstance | null, contextVNode: MountedVNode | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | null endNode: RenderNode | null
) { ) {
@ -149,14 +143,14 @@ export function createRenderer(options: RendererOptions) {
if (child.el) { if (child.el) {
children[i] = child = cloneVNode(child) children[i] = child = cloneVNode(child)
} }
mount(children[i], container, parentComponent, isSVG, endNode) mount(children[i], container, contextVNode, isSVG, endNode)
} }
} }
function mountElement( function mountElement(
vnode: VNode, vnode: VNode,
container: RenderNode | null, container: RenderNode | null,
parentComponent: ComponentInstance | null, contextVNode: MountedVNode | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | null endNode: RenderNode | null
) { ) {
@ -174,12 +168,12 @@ export function createRenderer(options: RendererOptions) {
if (childFlags !== ChildrenFlags.NO_CHILDREN) { if (childFlags !== ChildrenFlags.NO_CHILDREN) {
const hasSVGChildren = isSVG && tag !== 'foreignObject' const hasSVGChildren = isSVG && tag !== 'foreignObject'
if (childFlags & ChildrenFlags.SINGLE_VNODE) { 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) { } else if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {
mountArrayChildren( mountArrayChildren(
children as VNode[], children as VNode[],
el, el,
parentComponent, contextVNode,
hasSVGChildren, hasSVGChildren,
endNode endNode
) )
@ -207,10 +201,11 @@ export function createRenderer(options: RendererOptions) {
function mountStatefulComponent( function mountStatefulComponent(
vnode: VNode, vnode: VNode,
container: RenderNode | null, container: RenderNode | null,
parentComponent: ComponentInstance | null, contextVNode: MountedVNode | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | null endNode: RenderNode | null
) { ) {
vnode.contextVNode = contextVNode
if (vnode.flags & VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE) { if (vnode.flags & VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE) {
// kept-alive // kept-alive
activateComponentInstance(vnode) activateComponentInstance(vnode)
@ -219,7 +214,7 @@ export function createRenderer(options: RendererOptions) {
vnode, vnode,
vnode.tag as ComponentClass, vnode.tag as ComponentClass,
container, container,
parentComponent, contextVNode,
isSVG, isSVG,
endNode endNode
) )
@ -229,10 +224,11 @@ export function createRenderer(options: RendererOptions) {
function mountFunctionalComponent( function mountFunctionalComponent(
vnode: VNode, vnode: VNode,
container: RenderNode | null, container: RenderNode | null,
parentComponent: ComponentInstance | null, contextVNode: MountedVNode | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | null endNode: RenderNode | null
) { ) {
vnode.contextVNode = contextVNode
const { tag, data, slots } = vnode const { tag, data, slots } = vnode
const render = tag as FunctionalComponent const render = tag as FunctionalComponent
const { props, attrs } = resolveProps(data, render.props) const { props, attrs } = resolveProps(data, render.props)
@ -240,7 +236,7 @@ export function createRenderer(options: RendererOptions) {
render(props, slots || EMPTY_OBJ, attrs || EMPTY_OBJ), render(props, slots || EMPTY_OBJ, attrs || EMPTY_OBJ),
vnode vnode
)) ))
mount(subTree, container, parentComponent, isSVG, endNode) mount(subTree, container, vnode as MountedVNode, isSVG, endNode)
vnode.el = subTree.el as RenderNode vnode.el = subTree.el as RenderNode
} }
@ -258,14 +254,14 @@ export function createRenderer(options: RendererOptions) {
function mountFragment( function mountFragment(
vnode: VNode, vnode: VNode,
container: RenderNode | null, container: RenderNode | null,
parentComponent: ComponentInstance | null, contextVNode: MountedVNode | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | null endNode: RenderNode | null
) { ) {
const { children, childFlags } = vnode const { children, childFlags } = vnode
switch (childFlags) { switch (childFlags) {
case ChildrenFlags.SINGLE_VNODE: 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 vnode.el = (children as MountedVNode).el
break break
case ChildrenFlags.NO_CHILDREN: case ChildrenFlags.NO_CHILDREN:
@ -277,7 +273,7 @@ export function createRenderer(options: RendererOptions) {
mountArrayChildren( mountArrayChildren(
children as VNode[], children as VNode[],
container, container,
parentComponent, contextVNode,
isSVG, isSVG,
endNode endNode
) )
@ -288,7 +284,7 @@ export function createRenderer(options: RendererOptions) {
function mountPortal( function mountPortal(
vnode: VNode, vnode: VNode,
container: RenderNode | null, container: RenderNode | null,
parentComponent: ComponentInstance | null contextVNode: MountedVNode | null
) { ) {
const { tag, children, childFlags, ref } = vnode const { tag, children, childFlags, ref } = vnode
const target = typeof tag === 'string' ? platformQuerySelector(tag) : tag const target = typeof tag === 'string' ? platformQuerySelector(tag) : tag
@ -298,18 +294,12 @@ export function createRenderer(options: RendererOptions) {
} }
if (childFlags & ChildrenFlags.SINGLE_VNODE) { if (childFlags & ChildrenFlags.SINGLE_VNODE) {
mount( mount(children as VNode, target as RenderNode, contextVNode, false, null)
children as VNode,
target as RenderNode,
parentComponent,
false,
null
)
} else if (childFlags & ChildrenFlags.MULTIPLE_VNODES) { } else if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {
mountArrayChildren( mountArrayChildren(
children as VNode[], children as VNode[],
target as RenderNode, target as RenderNode,
parentComponent, contextVNode,
false, false,
null null
) )
@ -352,24 +342,24 @@ export function createRenderer(options: RendererOptions) {
prevVNode: MountedVNode, prevVNode: MountedVNode,
nextVNode: VNode, nextVNode: VNode,
container: RenderNode, container: RenderNode,
parentComponent: ComponentInstance | null, contextVNode: MountedVNode | null,
isSVG: boolean isSVG: boolean
) { ) {
const nextFlags = nextVNode.flags const nextFlags = nextVNode.flags
const prevFlags = prevVNode.flags const prevFlags = prevVNode.flags
if (prevFlags !== nextFlags) { if (prevFlags !== nextFlags) {
replaceVNode(prevVNode, nextVNode, container, parentComponent, isSVG) replaceVNode(prevVNode, nextVNode, container, contextVNode, isSVG)
} else if (nextFlags & VNodeFlags.ELEMENT) { } else if (nextFlags & VNodeFlags.ELEMENT) {
patchElement(prevVNode, nextVNode, container, parentComponent, isSVG) patchElement(prevVNode, nextVNode, container, contextVNode, isSVG)
} else if (nextFlags & VNodeFlags.COMPONENT) { } else if (nextFlags & VNodeFlags.COMPONENT) {
patchComponent(prevVNode, nextVNode, container, parentComponent, isSVG) patchComponent(prevVNode, nextVNode, container, contextVNode, isSVG)
} else if (nextFlags & VNodeFlags.TEXT) { } else if (nextFlags & VNodeFlags.TEXT) {
patchText(prevVNode, nextVNode) patchText(prevVNode, nextVNode)
} else if (nextFlags & VNodeFlags.FRAGMENT) { } else if (nextFlags & VNodeFlags.FRAGMENT) {
patchFragment(prevVNode, nextVNode, container, parentComponent, isSVG) patchFragment(prevVNode, nextVNode, container, contextVNode, isSVG)
} else if (nextFlags & VNodeFlags.PORTAL) { } else if (nextFlags & VNodeFlags.PORTAL) {
patchPortal(prevVNode, nextVNode, parentComponent) patchPortal(prevVNode, nextVNode, contextVNode)
} }
} }
@ -377,14 +367,14 @@ export function createRenderer(options: RendererOptions) {
prevVNode: MountedVNode, prevVNode: MountedVNode,
nextVNode: VNode, nextVNode: VNode,
container: RenderNode, container: RenderNode,
parentComponent: ComponentInstance | null, contextVNode: MountedVNode | null,
isSVG: boolean isSVG: boolean
) { ) {
const { flags, tag } = nextVNode const { flags, tag } = nextVNode
isSVG = isSVG || (flags & VNodeFlags.ELEMENT_SVG) > 0 isSVG = isSVG || (flags & VNodeFlags.ELEMENT_SVG) > 0
if (prevVNode.tag !== tag) { if (prevVNode.tag !== tag) {
replaceVNode(prevVNode, nextVNode, container, parentComponent, isSVG) replaceVNode(prevVNode, nextVNode, container, contextVNode, isSVG)
return return
} }
@ -434,7 +424,7 @@ export function createRenderer(options: RendererOptions) {
prevVNode.children, prevVNode.children,
nextVNode.children, nextVNode.children,
el, el,
parentComponent, contextVNode,
isSVG && nextVNode.tag !== 'foreignObject', isSVG && nextVNode.tag !== 'foreignObject',
null null
) )
@ -450,12 +440,13 @@ export function createRenderer(options: RendererOptions) {
prevVNode: MountedVNode, prevVNode: MountedVNode,
nextVNode: VNode, nextVNode: VNode,
container: RenderNode, container: RenderNode,
parentComponent: ComponentInstance | null, contextVNode: MountedVNode | null,
isSVG: boolean isSVG: boolean
) { ) {
nextVNode.contextVNode = contextVNode
const { tag, flags } = nextVNode const { tag, flags } = nextVNode
if (tag !== prevVNode.tag) { if (tag !== prevVNode.tag) {
replaceVNode(prevVNode, nextVNode, container, parentComponent, isSVG) replaceVNode(prevVNode, nextVNode, container, contextVNode, isSVG)
} else if (flags & VNodeFlags.COMPONENT_STATEFUL) { } else if (flags & VNodeFlags.COMPONENT_STATEFUL) {
patchStatefulComponent(prevVNode, nextVNode) patchStatefulComponent(prevVNode, nextVNode)
} else { } else {
@ -463,7 +454,7 @@ export function createRenderer(options: RendererOptions) {
prevVNode, prevVNode,
nextVNode, nextVNode,
container, container,
parentComponent, contextVNode,
isSVG isSVG
) )
} }
@ -497,7 +488,7 @@ export function createRenderer(options: RendererOptions) {
if (shouldForceUpdate) { if (shouldForceUpdate) {
instance.$forceUpdate() instance.$forceUpdate()
} else if (instance.$vnode.flags & VNodeFlags.COMPONENT) { } else if (instance.$vnode.flags & VNodeFlags.COMPONENT) {
instance.$vnode.parentVNode = nextVNode instance.$vnode.contextVNode = nextVNode
} }
nextVNode.el = instance.$vnode.el nextVNode.el = instance.$vnode.el
} }
@ -506,7 +497,7 @@ export function createRenderer(options: RendererOptions) {
prevVNode: MountedVNode, prevVNode: MountedVNode,
nextVNode: VNode, nextVNode: VNode,
container: RenderNode, container: RenderNode,
parentComponent: ComponentInstance | null, contextVNode: MountedVNode | null,
isSVG: boolean isSVG: boolean
) { ) {
// functional component tree is stored on the vnode as `children` // 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), render(props, nextSlots || EMPTY_OBJ, attrs || EMPTY_OBJ),
nextVNode nextVNode
)) ))
patch(prevTree, nextTree, container, parentComponent, isSVG) patch(prevTree, nextTree, container, nextVNode as MountedVNode, isSVG)
nextVNode.el = nextTree.el nextVNode.el = nextTree.el
} else if (prevTree.flags & VNodeFlags.COMPONENT) { } else if (prevTree.flags & VNodeFlags.COMPONENT) {
// functional component returned another component // functional component returned another component
prevTree.parentVNode = nextVNode prevTree.contextVNode = nextVNode
} }
} }
@ -538,7 +529,7 @@ export function createRenderer(options: RendererOptions) {
prevVNode: MountedVNode, prevVNode: MountedVNode,
nextVNode: VNode, nextVNode: VNode,
container: RenderNode, container: RenderNode,
parentComponent: ComponentInstance | null, contextVNode: MountedVNode | null,
isSVG: boolean isSVG: boolean
) { ) {
// determine the tail node of the previous fragment, // determine the tail node of the previous fragment,
@ -551,7 +542,7 @@ export function createRenderer(options: RendererOptions) {
prevVNode.children, prevVNode.children,
children, children,
container, container,
parentComponent, contextVNode,
isSVG, isSVG,
endNode endNode
) )
@ -595,7 +586,7 @@ export function createRenderer(options: RendererOptions) {
function patchPortal( function patchPortal(
prevVNode: MountedVNode, prevVNode: MountedVNode,
nextVNode: VNode, nextVNode: VNode,
parentComponent: ComponentInstance | null contextVNode: MountedVNode | null
) { ) {
const prevContainer = prevVNode.tag as RenderNode const prevContainer = prevVNode.tag as RenderNode
const nextContainer = nextVNode.tag as RenderNode const nextContainer = nextVNode.tag as RenderNode
@ -606,7 +597,7 @@ export function createRenderer(options: RendererOptions) {
prevVNode.children, prevVNode.children,
nextChildren, nextChildren,
prevContainer, prevContainer,
parentComponent, contextVNode,
false, false,
null null
) )
@ -631,12 +622,12 @@ export function createRenderer(options: RendererOptions) {
prevVNode: MountedVNode, prevVNode: MountedVNode,
nextVNode: VNode, nextVNode: VNode,
container: RenderNode, container: RenderNode,
parentComponent: ComponentInstance | null, contextVNode: MountedVNode | null,
isSVG: boolean isSVG: boolean
) { ) {
const refNode = platformNextSibling(getVNodeLastEl(prevVNode)) const refNode = platformNextSibling(getVNodeLastEl(prevVNode))
removeVNode(prevVNode, container) removeVNode(prevVNode, container)
mount(nextVNode, container, parentComponent, isSVG, refNode) mount(nextVNode, container, contextVNode, isSVG, refNode)
} }
function patchChildren( function patchChildren(
@ -645,7 +636,7 @@ export function createRenderer(options: RendererOptions) {
prevChildren: VNodeChildren, prevChildren: VNodeChildren,
nextChildren: VNodeChildren, nextChildren: VNodeChildren,
container: RenderNode, container: RenderNode,
parentComponent: ComponentInstance | null, contextVNode: MountedVNode | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | null endNode: RenderNode | null
) { ) {
@ -657,7 +648,7 @@ export function createRenderer(options: RendererOptions) {
prevChildren as MountedVNode, prevChildren as MountedVNode,
nextChildren as VNode, nextChildren as VNode,
container, container,
parentComponent, contextVNode,
isSVG isSVG
) )
break break
@ -669,7 +660,7 @@ export function createRenderer(options: RendererOptions) {
mountArrayChildren( mountArrayChildren(
nextChildren as VNode[], nextChildren as VNode[],
container, container,
parentComponent, contextVNode,
isSVG, isSVG,
endNode endNode
) )
@ -682,7 +673,7 @@ export function createRenderer(options: RendererOptions) {
mount( mount(
nextChildren as VNode, nextChildren as VNode,
container, container,
parentComponent, contextVNode,
isSVG, isSVG,
endNode endNode
) )
@ -693,7 +684,7 @@ export function createRenderer(options: RendererOptions) {
mountArrayChildren( mountArrayChildren(
nextChildren as VNode[], nextChildren as VNode[],
container, container,
parentComponent, contextVNode,
isSVG, isSVG,
endNode endNode
) )
@ -704,13 +695,7 @@ export function createRenderer(options: RendererOptions) {
// MULTIPLE_CHILDREN // MULTIPLE_CHILDREN
if (nextChildFlags === ChildrenFlags.SINGLE_VNODE) { if (nextChildFlags === ChildrenFlags.SINGLE_VNODE) {
removeChildren(prevChildren as MountedVNode[], container, endNode) removeChildren(prevChildren as MountedVNode[], container, endNode)
mount( mount(nextChildren as VNode, container, contextVNode, isSVG, endNode)
nextChildren as VNode,
container,
parentComponent,
isSVG,
endNode
)
} else if (nextChildFlags === ChildrenFlags.NO_CHILDREN) { } else if (nextChildFlags === ChildrenFlags.NO_CHILDREN) {
removeChildren(prevChildren as MountedVNode[], container, endNode) removeChildren(prevChildren as MountedVNode[], container, endNode)
} else { } else {
@ -721,7 +706,7 @@ export function createRenderer(options: RendererOptions) {
mountArrayChildren( mountArrayChildren(
nextChildren as VNode[], nextChildren as VNode[],
container, container,
parentComponent, contextVNode,
isSVG, isSVG,
endNode endNode
) )
@ -738,7 +723,7 @@ export function createRenderer(options: RendererOptions) {
container, container,
prevLength, prevLength,
nextLength, nextLength,
parentComponent, contextVNode,
isSVG, isSVG,
endNode endNode
) )
@ -749,7 +734,7 @@ export function createRenderer(options: RendererOptions) {
container, container,
prevLength, prevLength,
nextLength, nextLength,
parentComponent, contextVNode,
isSVG, isSVG,
endNode endNode
) )
@ -765,7 +750,7 @@ export function createRenderer(options: RendererOptions) {
container: RenderNode, container: RenderNode,
prevLength: number, prevLength: number,
nextLength: number, nextLength: number,
parentComponent: ComponentInstance | null, contextVNode: MountedVNode | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | null endNode: RenderNode | null
) { ) {
@ -779,7 +764,7 @@ export function createRenderer(options: RendererOptions) {
if (nextChild.el) { if (nextChild.el) {
nextChildren[i] = nextChild = cloneVNode(nextChild) nextChildren[i] = nextChild = cloneVNode(nextChild)
} }
patch(prevChild, nextChild, container, parentComponent, isSVG) patch(prevChild, nextChild, container, contextVNode, isSVG)
prevChildren[i] = nextChild as MountedVNode prevChildren[i] = nextChild as MountedVNode
} }
if (prevLength < nextLength) { if (prevLength < nextLength) {
@ -788,7 +773,7 @@ export function createRenderer(options: RendererOptions) {
if (nextChild.el) { if (nextChild.el) {
nextChildren[i] = nextChild = cloneVNode(nextChild) nextChildren[i] = nextChild = cloneVNode(nextChild)
} }
mount(nextChild, container, parentComponent, isSVG, endNode) mount(nextChild, container, contextVNode, isSVG, endNode)
} }
} else if (prevLength > nextLength) { } else if (prevLength > nextLength) {
for (i = commonLength; i < prevLength; i++) { for (i = commonLength; i < prevLength; i++) {
@ -803,7 +788,7 @@ export function createRenderer(options: RendererOptions) {
container: RenderNode, container: RenderNode,
prevLength: number, prevLength: number,
nextLength: number, nextLength: number,
parentComponent: ComponentInstance | null, contextVNode: MountedVNode | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | null endNode: RenderNode | null
) { ) {
@ -821,7 +806,7 @@ export function createRenderer(options: RendererOptions) {
if (nextVNode.el) { if (nextVNode.el) {
nextChildren[j] = nextVNode = cloneVNode(nextVNode) nextChildren[j] = nextVNode = cloneVNode(nextVNode)
} }
patch(prevVNode, nextVNode, container, parentComponent, isSVG) patch(prevVNode, nextVNode, container, contextVNode, isSVG)
prevChildren[j] = nextVNode as MountedVNode prevChildren[j] = nextVNode as MountedVNode
j++ j++
if (j > prevEnd || j > nextEnd) { if (j > prevEnd || j > nextEnd) {
@ -839,7 +824,7 @@ export function createRenderer(options: RendererOptions) {
if (nextVNode.el) { if (nextVNode.el) {
nextChildren[nextEnd] = nextVNode = cloneVNode(nextVNode) nextChildren[nextEnd] = nextVNode = cloneVNode(nextVNode)
} }
patch(prevVNode, nextVNode, container, parentComponent, isSVG) patch(prevVNode, nextVNode, container, contextVNode, isSVG)
prevChildren[prevEnd] = nextVNode as MountedVNode prevChildren[prevEnd] = nextVNode as MountedVNode
prevEnd-- prevEnd--
nextEnd-- nextEnd--
@ -862,7 +847,7 @@ export function createRenderer(options: RendererOptions) {
nextChildren[j] = nextVNode = cloneVNode(nextVNode) nextChildren[j] = nextVNode = cloneVNode(nextVNode)
} }
j++ j++
mount(nextVNode, container, parentComponent, isSVG, nextNode) mount(nextVNode, container, contextVNode, isSVG, nextNode)
} }
} }
} else if (j > nextEnd) { } else if (j > nextEnd) {
@ -907,7 +892,7 @@ export function createRenderer(options: RendererOptions) {
if (nextVNode.el) { if (nextVNode.el) {
nextChildren[j] = nextVNode = cloneVNode(nextVNode) nextChildren[j] = nextVNode = cloneVNode(nextVNode)
} }
patch(prevVNode, nextVNode, container, parentComponent, isSVG) patch(prevVNode, nextVNode, container, contextVNode, isSVG)
patched++ patched++
break break
} }
@ -951,7 +936,7 @@ export function createRenderer(options: RendererOptions) {
if (nextVNode.el) { if (nextVNode.el) {
nextChildren[j] = nextVNode = cloneVNode(nextVNode) nextChildren[j] = nextVNode = cloneVNode(nextVNode)
} }
patch(prevVNode, nextVNode, container, parentComponent, isSVG) patch(prevVNode, nextVNode, container, contextVNode, isSVG)
patched++ patched++
} else if (!canRemoveWholeContent) { } else if (!canRemoveWholeContent) {
removeVNode(prevVNode, container) removeVNode(prevVNode, container)
@ -967,7 +952,7 @@ export function createRenderer(options: RendererOptions) {
mountArrayChildren( mountArrayChildren(
nextChildren, nextChildren,
container, container,
parentComponent, contextVNode,
isSVG, isSVG,
endNode endNode
) )
@ -986,7 +971,7 @@ export function createRenderer(options: RendererOptions) {
mount( mount(
nextVNode, nextVNode,
container, container,
parentComponent, contextVNode,
isSVG, isSVG,
nextPos < nextLength ? nextChildren[nextPos].el : endNode nextPos < nextLength ? nextChildren[nextPos].el : endNode
) )
@ -1017,7 +1002,7 @@ export function createRenderer(options: RendererOptions) {
mount( mount(
nextVNode, nextVNode,
container, container,
parentComponent, contextVNode,
isSVG, isSVG,
nextPos < nextLength ? nextChildren[nextPos].el : endNode nextPos < nextLength ? nextChildren[nextPos].el : endNode
) )
@ -1149,18 +1134,18 @@ export function createRenderer(options: RendererOptions) {
// Component lifecycle ------------------------------------------------------- // Component lifecycle -------------------------------------------------------
function mountComponentInstance( function mountComponentInstance(
parentVNode: VNode, vnode: VNode,
Component: ComponentClass, Component: ComponentClass,
container: RenderNode | null, container: RenderNode | null,
parentComponent: ComponentInstance | null, contextVNode: MountedVNode | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | null endNode: RenderNode | null
): RenderNode { ): RenderNode {
// a vnode may already have an instance if this is a compat call with // a vnode may already have an instance if this is a compat call with
// new Vue() // new Vue()
const instance = const instance =
(__COMPAT__ && (parentVNode.children as ComponentInstance)) || (__COMPAT__ && (vnode.children as ComponentInstance)) ||
createComponentInstance(parentVNode, Component, parentComponent) createComponentInstance(vnode, Component, contextVNode)
// inject platform-specific unmount to keep-alive container // inject platform-specific unmount to keep-alive container
if ((Component as any)[KeepAliveSymbol] === true) { if ((Component as any)[KeepAliveSymbol] === true) {
@ -1185,16 +1170,22 @@ export function createRenderer(options: RendererOptions) {
} else { } else {
// this will be executed synchronously right here // this will be executed synchronously right here
instance.$vnode = renderInstanceRoot(instance) as MountedVNode instance.$vnode = renderInstanceRoot(instance) as MountedVNode
mount(instance.$vnode, container, instance, isSVG, endNode) mount(
parentVNode.el = instance.$vnode.el instance.$vnode,
container,
vnode as MountedVNode,
isSVG,
endNode
)
vnode.el = instance.$vnode.el
if (__DEV__) { if (__DEV__) {
// expose __vue__ for devtools // expose __vue__ for devtools
;(parentVNode.el as any).__vue__ = instance ;(vnode.el as any).__vue__ = instance
} }
instance._mounted = true 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( function mountComponentInstanceCallbacks(
@ -1212,7 +1203,7 @@ export function createRenderer(options: RendererOptions) {
ref: Ref | null ref: Ref | null
) { ) {
if (ref) { if (ref) {
mountRef(ref, instance) mountRef(ref, instance.$proxy)
} }
if (instance.mounted) { if (instance.mounted) {
lifecycleHooks.push(() => { lifecycleHooks.push(() => {
@ -1235,7 +1226,13 @@ export function createRenderer(options: RendererOptions) {
instance instance
) as MountedVNode) ) as MountedVNode)
const container = platformParentNode(prevVNode.el) as RenderNode 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 const el = nextVNode.el as RenderNode
if (__DEV__) { if (__DEV__) {
@ -1243,14 +1240,14 @@ export function createRenderer(options: RendererOptions) {
;(el as any).__vue__ = instance ;(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) { if ((nextVNode.flags & VNodeFlags.PORTAL) === 0) {
let vnode = instance.$parentVNode let vnode = instance.$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
} }
vnode = vnode.parentVNode vnode = vnode.contextVNode
} }
} }
@ -1354,7 +1351,10 @@ export function createRenderer(options: RendererOptions) {
// API ----------------------------------------------------------------------- // API -----------------------------------------------------------------------
function render(vnode: VNode | null, container: any) { function render(
vnode: VNode | null,
container: any
): ComponentInstance | null {
const prevVNode = container.vnode const prevVNode = container.vnode
if (vnode && vnode.el) { if (vnode && vnode.el) {
vnode = cloneVNode(vnode) vnode = cloneVNode(vnode)

View File

@ -24,7 +24,12 @@ export { createAsyncComponent } from './optional/asyncComponent'
export { KeepAlive } from './optional/keepAlive' export { KeepAlive } from './optional/keepAlive'
// flags & types // flags & types
export { ComponentType, ComponentClass, FunctionalComponent } from './component' export {
ComponentType,
ComponentClass,
FunctionalComponent,
ComponentInstance
} from './component'
export * from './componentOptions' export * from './componentOptions'
export { VNodeFlags, ChildrenFlags } from './flags' export { VNodeFlags, ChildrenFlags } from './flags'
export { VNode, Slots } from './vdom' export { VNode, Slots } from './vdom'

View File

@ -31,6 +31,9 @@ export interface VNode {
// points to parent component's placeholder vnode // points to parent component's placeholder vnode
// this is used to update vnode.el for nested HOCs. // this is used to update vnode.el for nested HOCs.
parentVNode: VNode | null 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 { export interface MountedVNode extends VNode {
@ -84,7 +87,8 @@ export function createVNode(
ref: ref === void 0 ? null : ref, ref: ref === void 0 ? null : ref,
slots: slots === void 0 ? null : slots, slots: slots === void 0 ? null : slots,
el: null, el: null,
parentVNode: null parentVNode: null,
contextVNode: null
} }
if (childFlags === ChildrenFlags.UNKNOWN_CHILDREN) { if (childFlags === ChildrenFlags.UNKNOWN_CHILDREN) {
normalizeChildren(vnode, children) normalizeChildren(vnode, children)

View File

@ -1,4 +1,4 @@
import { createRenderer, VNode } from '@vue/core' import { createRenderer, VNode, ComponentInstance } from '@vue/core'
import { nodeOps } from './nodeOps' import { nodeOps } from './nodeOps'
import { patchData } from './patchData' import { patchData } from './patchData'
import { teardownVNode } from './teardownVNode' import { teardownVNode } from './teardownVNode'
@ -9,7 +9,10 @@ const { render: _render } = createRenderer({
teardownVNode teardownVNode
}) })
type publicRender = (node: VNode | null, container: HTMLElement) => void type publicRender = (
node: VNode | null,
container: HTMLElement
) => ComponentInstance | null
export const render = _render as publicRender export const render = _render as publicRender
// re-export everything from core // re-export everything from core

View File

@ -1,4 +1,4 @@
import { createRenderer, VNode } from '@vue/core' import { createRenderer, VNode, ComponentInstance } from '@vue/core'
import { nodeOps, TestElement } from './nodeOps' import { nodeOps, TestElement } from './nodeOps'
import { patchData } from './patchData' import { patchData } from './patchData'
@ -7,7 +7,10 @@ const { render: _render } = createRenderer({
patchData 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 const render = _render as publicRender
export { serialize } from './serialize' export { serialize } from './serialize'