refactor: improve fragment implementation

This commit is contained in:
Evan You 2018-10-02 13:59:11 -04:00
parent 0ae6d8ab8b
commit adfe0ee7bf
4 changed files with 193 additions and 227 deletions

View File

@ -1,5 +1,5 @@
import { EMPTY_OBJ } from './utils' import { EMPTY_OBJ } from './utils'
import { VNode, Slots, RenderNode, RenderFragment } from './vdom' import { VNode, Slots, RenderNode, MountedVNode } from './vdom'
import { import {
Data, Data,
RenderFunction, RenderFunction,
@ -30,7 +30,7 @@ export type ComponentType = ComponentClass | FunctionalComponent
// to represent a mounted component // to represent a mounted component
export interface MountedComponent<D = Data, P = Data> export interface MountedComponent<D = Data, P = Data>
extends InternalComponent { extends InternalComponent {
$vnode: VNode $vnode: MountedVNode
$data: D $data: D
$props: P $props: P
$attrs: Data $attrs: Data
@ -68,7 +68,7 @@ export interface MountedComponent<D = Data, P = Data>
class InternalComponent { class InternalComponent {
public static options?: ComponentOptions public static options?: ComponentOptions
public get $el(): RenderNode | RenderFragment | null { public get $el(): RenderNode | null {
return this.$vnode && this.$vnode.el return this.$vnode && this.$vnode.el
} }

View File

@ -1,7 +1,7 @@
import { VNodeFlags } from './flags' import { VNodeFlags } from './flags'
import { EMPTY_OBJ } from './utils' import { EMPTY_OBJ } from './utils'
import { h } from './h' import { h } from './h'
import { VNode, createFragment } from './vdom' import { VNode, MountedVNode, createFragment } from './vdom'
import { Component, MountedComponent, ComponentClass } from './component' import { Component, MountedComponent, ComponentClass } from './component'
import { createTextVNode, cloneVNode } from './vdom' import { createTextVNode, cloneVNode } from './vdom'
import { initializeState } from './componentState' import { initializeState } from './componentState'
@ -22,7 +22,7 @@ export function createComponentInstance(
parentComponent: MountedComponent | null parentComponent: MountedComponent | null
): MountedComponent { ): MountedComponent {
const instance = (vnode.children = new Component()) as MountedComponent const instance = (vnode.children = new Component()) as MountedComponent
instance.$parentVNode = vnode instance.$parentVNode = vnode as MountedVNode
// renderProxy // renderProxy
const proxy = (instance.$proxy = createRenderProxy(instance)) const proxy = (instance.$proxy = createRenderProxy(instance))
@ -53,7 +53,7 @@ export function createComponentInstance(
return instance as MountedComponent return instance as MountedComponent
} }
export function renderInstanceRoot(instance: MountedComponent) { export function renderInstanceRoot(instance: MountedComponent): VNode {
let vnode let vnode
try { try {
vnode = instance.render.call( vnode = instance.render.call(

View File

@ -5,13 +5,11 @@ import { EMPTY_OBJ, reservedPropRE, lis } from './utils'
import { import {
VNode, VNode,
MountedVNode, MountedVNode,
MountedVNodes,
RenderNode, RenderNode,
createTextVNode, createTextVNode,
cloneVNode, cloneVNode,
Ref, Ref,
VNodeChildren, VNodeChildren
RenderFragment
} from './vdom' } from './vdom'
import { import {
MountedComponent, MountedComponent,
@ -34,7 +32,6 @@ interface NodeOps {
setText: (node: any, text: string) => void setText: (node: any, text: string) => void
appendChild: (parent: any, child: any) => void appendChild: (parent: any, child: any) => void
insertBefore: (parent: any, child: any, ref: any) => void insertBefore: (parent: any, child: any, ref: any) => void
replaceChild: (parent: any, oldChild: any, newChild: any) => void
removeChild: (parent: any, child: any) => void removeChild: (parent: any, child: any) => void
clearContent: (node: any) => void clearContent: (node: any) => void
parentNode: (node: any) => any parentNode: (node: any) => any
@ -75,7 +72,6 @@ export function createRenderer(options: RendererOptions) {
setText: platformSetText, setText: platformSetText,
appendChild: platformAppendChild, appendChild: platformAppendChild,
insertBefore: platformInsertBefore, insertBefore: platformInsertBefore,
replaceChild: platformReplaceChild,
removeChild: platformRemoveChild, removeChild: platformRemoveChild,
clearContent: platformClearContent, clearContent: platformClearContent,
parentNode: platformParentNode, parentNode: platformParentNode,
@ -86,78 +82,19 @@ export function createRenderer(options: RendererOptions) {
teardownVNode teardownVNode
} = options } = options
// Node operations (shimmed to handle virtual fragments) ---------------------
function appendChild(container: RenderNode, el: RenderNode | RenderFragment) {
if (el.$f) {
for (let i = 0; i < el.children.length; i++) {
appendChild(container, el.children[i])
}
} else {
platformAppendChild(container, el)
}
}
function insertBefore(
container: RenderNode,
el: RenderNode | RenderFragment,
ref: RenderNode | RenderFragment
) {
while (ref.$f) {
ref = ref.children[0]
}
if (el.$f) {
for (let i = 0; i < el.children.length; i++) {
insertBefore(container, el.children[i], ref)
}
} else {
platformInsertBefore(container, el, ref)
}
}
function removeChild(container: RenderNode, el: RenderNode | RenderFragment) {
if (el.$f) {
for (let i = 0; i < el.children.length; i++) {
removeChild(container, el.children[i])
}
} else {
platformRemoveChild(container, el)
}
}
function replaceChild(
container: RenderNode,
oldChild: RenderNode | RenderFragment,
newChild: RenderNode | RenderFragment
) {
if (oldChild.$f || newChild.$f) {
insertOrAppend(container, newChild, oldChild)
removeChild(container, oldChild)
} else {
platformReplaceChild(container, oldChild, newChild)
}
}
function parentNode(el: RenderNode | RenderFragment): RenderNode {
while (el.$f) {
el = el.children[0]
}
return platformParentNode(el)
}
function insertOrAppend( function insertOrAppend(
container: RenderNode, container: RenderNode,
newNode: RenderNode | RenderFragment, newNode: RenderNode,
refNode: RenderNode | RenderFragment | null refNode: RenderNode | null
) { ) {
if (refNode === null) { if (refNode === null) {
appendChild(container, newNode) platformAppendChild(container, newNode)
} else { } else {
insertBefore(container, newNode, refNode) platformInsertBefore(container, newNode, refNode)
} }
} }
// lifecycle lifecycleHooks ----------------------------------------------------------- // Lifecycle Hooks -----------------------------------------------------------
const lifecycleHooks: Function[] = [] const lifecycleHooks: Function[] = []
const vnodeUpdatedHooks: Function[] = [] const vnodeUpdatedHooks: Function[] = []
@ -176,7 +113,7 @@ export function createRenderer(options: RendererOptions) {
container: RenderNode | null, container: RenderNode | null,
parentComponent: MountedComponent | null, parentComponent: MountedComponent | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | RenderFragment | null endNode: RenderNode | null
) { ) {
const { flags } = vnode const { flags } = vnode
if (flags & VNodeFlags.ELEMENT) { if (flags & VNodeFlags.ELEMENT) {
@ -197,7 +134,7 @@ export function createRenderer(options: RendererOptions) {
container: RenderNode | null, container: RenderNode | null,
parentComponent: MountedComponent | null, parentComponent: MountedComponent | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | RenderFragment | null endNode: RenderNode | null
) { ) {
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
let child = children[i] let child = children[i]
@ -213,7 +150,7 @@ export function createRenderer(options: RendererOptions) {
container: RenderNode | null, container: RenderNode | null,
parentComponent: MountedComponent | null, parentComponent: MountedComponent | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | RenderFragment | null endNode: RenderNode | null
) { ) {
const { flags, tag, data, children, childFlags, ref } = vnode const { flags, tag, data, children, childFlags, ref } = vnode
isSVG = isSVG || (flags & VNodeFlags.ELEMENT_SVG) > 0 isSVG = isSVG || (flags & VNodeFlags.ELEMENT_SVG) > 0
@ -264,19 +201,18 @@ export function createRenderer(options: RendererOptions) {
container: RenderNode | null, container: RenderNode | null,
parentComponent: MountedComponent | null, parentComponent: MountedComponent | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | RenderFragment | null endNode: RenderNode | null
) { ) {
let el: RenderNode | RenderFragment
const { flags, tag, data, slots } = vnode const { flags, tag, data, slots } = vnode
if (flags & VNodeFlags.COMPONENT_STATEFUL) { if (flags & VNodeFlags.COMPONENT_STATEFUL) {
if (flags & VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE) { if (flags & VNodeFlags.COMPONENT_STATEFUL_KEPT_ALIVE) {
// kept-alive // kept-alive
el = activateComponentInstance(vnode) activateComponentInstance(vnode)
} else { } else {
el = mountComponentInstance( mountComponentInstance(
vnode, vnode,
tag as ComponentClass, tag as ComponentClass,
null, container,
parentComponent, parentComponent,
isSVG, isSVG,
endNode endNode
@ -292,24 +228,20 @@ export function createRenderer(options: RendererOptions) {
attrs, attrs,
render.inheritAttrs render.inheritAttrs
)) ))
mount(subTree, null, parentComponent, isSVG, endNode) mount(subTree, container, parentComponent, isSVG, endNode)
el = vnode.el = subTree.el as RenderNode vnode.el = subTree.el as RenderNode
}
if (container != null) {
insertOrAppend(container, el, endNode)
} }
} }
function mountText( function mountText(
vnode: VNode, vnode: VNode,
container: RenderNode | null, container: RenderNode | null,
endNode: RenderNode | RenderFragment | null endNode: RenderNode | null
): RenderNode { ) {
const el = (vnode.el = platformCreateText(vnode.children as string)) const el = (vnode.el = platformCreateText(vnode.children as string))
if (container != null) { if (container != null) {
insertOrAppend(container, el, endNode) insertOrAppend(container, el, endNode)
} }
return el
} }
function mountFragment( function mountFragment(
@ -317,18 +249,20 @@ export function createRenderer(options: RendererOptions) {
container: RenderNode | null, container: RenderNode | null,
parentComponent: MountedComponent | null, parentComponent: MountedComponent | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | RenderFragment | null endNode: RenderNode | null
): RenderFragment { ) {
const { children, childFlags } = vnode const { children, childFlags } = vnode
const fragment: RenderFragment = (vnode.el = { switch (childFlags) {
$f: true, case ChildrenFlags.SINGLE_VNODE:
children: []
})
const fragmentChildren = fragment.children
if (childFlags & ChildrenFlags.SINGLE_VNODE) {
mount(children as VNode, container, parentComponent, isSVG, endNode) mount(children as VNode, container, parentComponent, isSVG, endNode)
fragmentChildren.push((children as VNode).el as RenderNode) vnode.el = (children as MountedVNode).el
} else if (childFlags & ChildrenFlags.MULTIPLE_VNODES) { break
case ChildrenFlags.NO_CHILDREN:
const placeholder = createTextVNode('')
mountText(placeholder, container, null)
vnode.el = placeholder.el
break
default:
mountArrayChildren( mountArrayChildren(
children as VNode[], children as VNode[],
container, container,
@ -336,17 +270,8 @@ export function createRenderer(options: RendererOptions) {
isSVG, isSVG,
endNode endNode
) )
for (let i = 0; i < (children as MountedVNodes).length; i++) { vnode.el = (children as MountedVNode[])[0].el
fragmentChildren.push((children as MountedVNodes)[i].el)
} }
} else {
// ensure at least one children so that it can be used as a ref node
// during insertions
const vnode = createTextVNode('')
mountText(vnode, container, endNode)
fragmentChildren.push(vnode.el as RenderNode)
}
return fragment
} }
function mountPortal( function mountPortal(
@ -381,7 +306,9 @@ export function createRenderer(options: RendererOptions) {
if (ref) { if (ref) {
mountRef(ref, target as RenderNode) mountRef(ref, target as RenderNode)
} }
vnode.el = mountText(createTextVNode(''), container, null) const placeholder = createTextVNode('')
mountText(placeholder, container, null)
vnode.el = placeholder.el
} }
// patching ------------------------------------------------------------------ // patching ------------------------------------------------------------------
@ -411,7 +338,7 @@ export function createRenderer(options: RendererOptions) {
} }
function patch( function patch(
prevVNode: VNode, prevVNode: MountedVNode,
nextVNode: VNode, nextVNode: VNode,
container: RenderNode, container: RenderNode,
parentComponent: MountedComponent | null, parentComponent: MountedComponent | null,
@ -436,7 +363,7 @@ export function createRenderer(options: RendererOptions) {
} }
function patchElement( function patchElement(
prevVNode: VNode, prevVNode: MountedVNode,
nextVNode: VNode, nextVNode: VNode,
container: RenderNode, container: RenderNode,
parentComponent: MountedComponent | null, parentComponent: MountedComponent | null,
@ -450,7 +377,7 @@ export function createRenderer(options: RendererOptions) {
return return
} }
const el = (nextVNode.el = prevVNode.el) as RenderNode const el = (nextVNode.el = prevVNode.el)
const prevData = prevVNode.data const prevData = prevVNode.data
const nextData = nextVNode.data const nextData = nextVNode.data
@ -509,7 +436,7 @@ export function createRenderer(options: RendererOptions) {
} }
function patchComponent( function patchComponent(
prevVNode: VNode, prevVNode: MountedVNode,
nextVNode: VNode, nextVNode: VNode,
container: RenderNode, container: RenderNode,
parentComponent: MountedComponent | null, parentComponent: MountedComponent | null,
@ -531,7 +458,7 @@ export function createRenderer(options: RendererOptions) {
} }
} }
function patchStatefulComponent(prevVNode: VNode, nextVNode: VNode) { function patchStatefulComponent(prevVNode: MountedVNode, nextVNode: VNode) {
const { childFlags: prevChildFlags } = prevVNode const { childFlags: prevChildFlags } = prevVNode
const { const {
data: nextData, data: nextData,
@ -542,7 +469,7 @@ export function createRenderer(options: RendererOptions) {
const instance = (nextVNode.children = const instance = (nextVNode.children =
prevVNode.children) as MountedComponent prevVNode.children) as MountedComponent
instance.$slots = nextSlots || EMPTY_OBJ instance.$slots = nextSlots || EMPTY_OBJ
instance.$parentVNode = nextVNode instance.$parentVNode = nextVNode as MountedVNode
// Update props. This will trigger child update if necessary. // Update props. This will trigger child update if necessary.
if (nextData !== null) { if (nextData !== null) {
@ -565,7 +492,7 @@ export function createRenderer(options: RendererOptions) {
} }
function patchFunctionalComponent( function patchFunctionalComponent(
prevVNode: VNode, prevVNode: MountedVNode,
nextVNode: VNode, nextVNode: VNode,
container: RenderNode, container: RenderNode,
parentComponent: MountedComponent | null, parentComponent: MountedComponent | null,
@ -575,7 +502,7 @@ export function createRenderer(options: RendererOptions) {
const { data: prevData, slots: prevSlots } = prevVNode const { data: prevData, slots: prevSlots } = prevVNode
const { data: nextData, slots: nextSlots } = nextVNode const { data: nextData, slots: nextSlots } = nextVNode
const render = nextVNode.tag as FunctionalComponent const render = nextVNode.tag as FunctionalComponent
const prevTree = prevVNode.children as VNode const prevTree = prevVNode.children as MountedVNode
let shouldUpdate = true let shouldUpdate = true
if (render.pure && prevSlots == null && nextSlots == null) { if (render.pure && prevSlots == null && nextSlots == null) {
@ -599,7 +526,7 @@ export function createRenderer(options: RendererOptions) {
} }
function patchFragment( function patchFragment(
prevVNode: VNode, prevVNode: MountedVNode,
nextVNode: VNode, nextVNode: VNode,
container: RenderNode, container: RenderNode,
parentComponent: MountedComponent | null, parentComponent: MountedComponent | null,
@ -607,11 +534,8 @@ export function createRenderer(options: RendererOptions) {
) { ) {
// determine the tail node of the previous fragment, // determine the tail node of the previous fragment,
// then retrieve its next sibling to use as the end node for patchChildren. // then retrieve its next sibling to use as the end node for patchChildren.
let prevElement = prevVNode.el as RenderNode | RenderFragment const endNode = platformNextSibling(getVNodeLastEl(prevVNode))
while (prevElement.$f) { const { childFlags, children } = nextVNode
prevElement = prevElement.children[prevElement.children.length - 1]
}
const { children, childFlags } = nextVNode
patchChildren( patchChildren(
prevVNode.childFlags, prevVNode.childFlags,
childFlags, childFlags,
@ -620,24 +544,38 @@ export function createRenderer(options: RendererOptions) {
container, container,
parentComponent, parentComponent,
isSVG, isSVG,
platformNextSibling(prevElement) endNode
) )
nextVNode.el = prevVNode.el as RenderFragment switch (childFlags) {
const fragmentChildren: ( case ChildrenFlags.SINGLE_VNODE:
| RenderNode nextVNode.el = (children as MountedVNode).el
| RenderFragment)[] = (nextVNode.el.children = []) break
if (childFlags & ChildrenFlags.SINGLE_VNODE) { case ChildrenFlags.NO_CHILDREN:
fragmentChildren.push((children as MountedVNode).el) nextVNode.el = prevVNode.el
} else if (childFlags & ChildrenFlags.MULTIPLE_VNODES) { break
for (let i = 0; i < (children as MountedVNodes).length; i++) { default:
fragmentChildren.push((children as MountedVNodes)[i].el) nextVNode.el = (children as MountedVNode[])[0].el
}
} else {
fragmentChildren.push(mountText(createTextVNode(''), null, null))
} }
} }
function patchText(prevVNode: VNode, nextVNode: VNode) { function getVNodeLastEl(vnode: MountedVNode): RenderNode {
const { el, flags, children, childFlags } = vnode
if (flags & VNodeFlags.FRAGMENT) {
if (childFlags & ChildrenFlags.SINGLE_VNODE) {
return getVNodeLastEl(children as MountedVNode)
} else if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {
return getVNodeLastEl(
(children as MountedVNode[])[(children as MountedVNode[]).length - 1]
)
} else {
return el
}
} else {
return el
}
}
function patchText(prevVNode: MountedVNode, nextVNode: VNode) {
const el = (nextVNode.el = prevVNode.el) as RenderNode const el = (nextVNode.el = prevVNode.el) as RenderNode
const nextText = nextVNode.children const nextText = nextVNode.children
if (nextText !== prevVNode.children) { if (nextText !== prevVNode.children) {
@ -646,7 +584,7 @@ export function createRenderer(options: RendererOptions) {
} }
function patchPortal( function patchPortal(
prevVNode: VNode, prevVNode: MountedVNode,
nextVNode: VNode, nextVNode: VNode,
parentComponent: MountedComponent | null parentComponent: MountedComponent | null
) { ) {
@ -667,13 +605,13 @@ export function createRenderer(options: RendererOptions) {
if (nextContainer !== prevContainer) { if (nextContainer !== prevContainer) {
switch (nextVNode.childFlags) { switch (nextVNode.childFlags) {
case ChildrenFlags.SINGLE_VNODE: case ChildrenFlags.SINGLE_VNODE:
appendChild(nextContainer, (nextChildren as MountedVNode).el) moveVNode(nextChildren as MountedVNode, nextContainer, null)
break break
case ChildrenFlags.NO_CHILDREN: case ChildrenFlags.NO_CHILDREN:
break break
default: default:
for (let i = 0; i < (nextChildren as MountedVNodes).length; i++) { for (let i = 0; i < (nextChildren as MountedVNode[]).length; i++) {
appendChild(nextContainer, (nextChildren as MountedVNodes)[i].el) moveVNode((nextChildren as MountedVNode[])[i], nextContainer, null)
} }
break break
} }
@ -681,19 +619,15 @@ export function createRenderer(options: RendererOptions) {
} }
function replaceVNode( function replaceVNode(
prevVNode: VNode, prevVNode: MountedVNode,
nextVNode: VNode, nextVNode: VNode,
container: RenderNode, container: RenderNode,
parentComponent: MountedComponent | null, parentComponent: MountedComponent | null,
isSVG: boolean isSVG: boolean
) { ) {
unmount(prevVNode) const refNode = platformNextSibling(getVNodeLastEl(prevVNode))
mount(nextVNode, null, parentComponent, isSVG, null) removeVNode(prevVNode, container)
replaceChild( mount(nextVNode, container, parentComponent, isSVG, refNode)
container,
prevVNode.el as RenderNode | RenderFragment,
nextVNode.el as RenderNode
)
} }
function patchChildren( function patchChildren(
@ -704,14 +638,14 @@ export function createRenderer(options: RendererOptions) {
container: RenderNode, container: RenderNode,
parentComponent: MountedComponent | null, parentComponent: MountedComponent | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | RenderFragment | null endNode: RenderNode | null
) { ) {
switch (prevChildFlags) { switch (prevChildFlags) {
case ChildrenFlags.SINGLE_VNODE: case ChildrenFlags.SINGLE_VNODE:
switch (nextChildFlags) { switch (nextChildFlags) {
case ChildrenFlags.SINGLE_VNODE: case ChildrenFlags.SINGLE_VNODE:
patch( patch(
prevChildren as VNode, prevChildren as MountedVNode,
nextChildren as VNode, nextChildren as VNode,
container, container,
parentComponent, parentComponent,
@ -719,10 +653,10 @@ export function createRenderer(options: RendererOptions) {
) )
break break
case ChildrenFlags.NO_CHILDREN: case ChildrenFlags.NO_CHILDREN:
remove(prevChildren as VNode, container) removeVNode(prevChildren as MountedVNode, container)
break break
default: default:
remove(prevChildren as VNode, container) removeVNode(prevChildren as MountedVNode, container)
mountArrayChildren( mountArrayChildren(
nextChildren as VNode[], nextChildren as VNode[],
container, container,
@ -760,7 +694,7 @@ export function createRenderer(options: RendererOptions) {
default: default:
// MULTIPLE_CHILDREN // MULTIPLE_CHILDREN
if (nextChildFlags === ChildrenFlags.SINGLE_VNODE) { if (nextChildFlags === ChildrenFlags.SINGLE_VNODE) {
removeAll(prevChildren as MountedVNodes, container, endNode) removeChildren(prevChildren as MountedVNode[], container, endNode)
mount( mount(
nextChildren as VNode, nextChildren as VNode,
container, container,
@ -769,7 +703,7 @@ export function createRenderer(options: RendererOptions) {
endNode endNode
) )
} else if (nextChildFlags === ChildrenFlags.NO_CHILDREN) { } else if (nextChildFlags === ChildrenFlags.NO_CHILDREN) {
removeAll(prevChildren as MountedVNodes, container, endNode) removeChildren(prevChildren as MountedVNode[], container, endNode)
} else { } else {
const prevLength = (prevChildren as VNode[]).length const prevLength = (prevChildren as VNode[]).length
const nextLength = (nextChildren as VNode[]).length const nextLength = (nextChildren as VNode[]).length
@ -784,13 +718,13 @@ export function createRenderer(options: RendererOptions) {
) )
} }
} else if (nextLength === 0) { } else if (nextLength === 0) {
removeAll(prevChildren as MountedVNodes, container, endNode) removeChildren(prevChildren as MountedVNode[], container, endNode)
} else if ( } else if (
prevChildFlags === ChildrenFlags.KEYED_VNODES && prevChildFlags === ChildrenFlags.KEYED_VNODES &&
nextChildFlags === ChildrenFlags.KEYED_VNODES nextChildFlags === ChildrenFlags.KEYED_VNODES
) { ) {
patchKeyedChildren( patchKeyedChildren(
prevChildren as VNode[], prevChildren as MountedVNode[],
nextChildren as VNode[], nextChildren as VNode[],
container, container,
prevLength, prevLength,
@ -801,7 +735,7 @@ export function createRenderer(options: RendererOptions) {
) )
} else { } else {
patchNonKeyedChildren( patchNonKeyedChildren(
prevChildren as VNode[], prevChildren as MountedVNode[],
nextChildren as VNode[], nextChildren as VNode[],
container, container,
prevLength, prevLength,
@ -817,14 +751,14 @@ export function createRenderer(options: RendererOptions) {
} }
function patchNonKeyedChildren( function patchNonKeyedChildren(
prevChildren: VNode[], prevChildren: MountedVNode[],
nextChildren: VNode[], nextChildren: VNode[],
container: RenderNode, container: RenderNode,
prevLength: number, prevLength: number,
nextLength: number, nextLength: number,
parentComponent: MountedComponent | null, parentComponent: MountedComponent | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | RenderFragment | null endNode: RenderNode | null
) { ) {
const commonLength = prevLength > nextLength ? nextLength : prevLength const commonLength = prevLength > nextLength ? nextLength : prevLength
let i = 0 let i = 0
@ -837,7 +771,7 @@ export function createRenderer(options: RendererOptions) {
nextChildren[i] = nextChild = cloneVNode(nextChild) nextChildren[i] = nextChild = cloneVNode(nextChild)
} }
patch(prevChild, nextChild, container, parentComponent, isSVG) patch(prevChild, nextChild, container, parentComponent, isSVG)
prevChildren[i] = nextChild prevChildren[i] = nextChild as MountedVNode
} }
if (prevLength < nextLength) { if (prevLength < nextLength) {
for (i = commonLength; i < nextLength; i++) { for (i = commonLength; i < nextLength; i++) {
@ -849,20 +783,20 @@ export function createRenderer(options: RendererOptions) {
} }
} else if (prevLength > nextLength) { } else if (prevLength > nextLength) {
for (i = commonLength; i < prevLength; i++) { for (i = commonLength; i < prevLength; i++) {
remove(prevChildren[i], container) removeVNode(prevChildren[i], container)
} }
} }
} }
function patchKeyedChildren( function patchKeyedChildren(
prevChildren: VNode[], prevChildren: MountedVNode[],
nextChildren: VNode[], nextChildren: VNode[],
container: RenderNode, container: RenderNode,
prevLength: number, prevLength: number,
nextLength: number, nextLength: number,
parentComponent: MountedComponent | null, parentComponent: MountedComponent | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | RenderFragment | null endNode: RenderNode | null
) { ) {
let prevEnd = prevLength - 1 let prevEnd = prevLength - 1
let nextEnd = nextLength - 1 let nextEnd = nextLength - 1
@ -879,7 +813,7 @@ export function createRenderer(options: RendererOptions) {
nextChildren[j] = nextVNode = cloneVNode(nextVNode) nextChildren[j] = nextVNode = cloneVNode(nextVNode)
} }
patch(prevVNode, nextVNode, container, parentComponent, isSVG) patch(prevVNode, nextVNode, container, parentComponent, isSVG)
prevChildren[j] = nextVNode prevChildren[j] = nextVNode as MountedVNode
j++ j++
if (j > prevEnd || j > nextEnd) { if (j > prevEnd || j > nextEnd) {
break outer break outer
@ -897,7 +831,7 @@ export function createRenderer(options: RendererOptions) {
nextChildren[nextEnd] = nextVNode = cloneVNode(nextVNode) nextChildren[nextEnd] = nextVNode = cloneVNode(nextVNode)
} }
patch(prevVNode, nextVNode, container, parentComponent, isSVG) patch(prevVNode, nextVNode, container, parentComponent, isSVG)
prevChildren[prevEnd] = nextVNode prevChildren[prevEnd] = nextVNode as MountedVNode
prevEnd-- prevEnd--
nextEnd-- nextEnd--
if (j > prevEnd || j > nextEnd) { if (j > prevEnd || j > nextEnd) {
@ -924,7 +858,7 @@ export function createRenderer(options: RendererOptions) {
} }
} else if (j > nextEnd) { } else if (j > nextEnd) {
while (j <= prevEnd) { while (j <= prevEnd) {
remove(prevChildren[j++], container) removeVNode(prevChildren[j++], container)
} }
} else { } else {
let prevStart = j let prevStart = j
@ -953,7 +887,7 @@ export function createRenderer(options: RendererOptions) {
if (canRemoveWholeContent) { if (canRemoveWholeContent) {
canRemoveWholeContent = false canRemoveWholeContent = false
while (i > prevStart) { while (i > prevStart) {
remove(prevChildren[prevStart++], container) removeVNode(prevChildren[prevStart++], container)
} }
} }
if (pos > j) { if (pos > j) {
@ -970,10 +904,10 @@ export function createRenderer(options: RendererOptions) {
} }
} }
if (!canRemoveWholeContent && j > nextEnd) { if (!canRemoveWholeContent && j > nextEnd) {
remove(prevVNode, container) removeVNode(prevVNode, container)
} }
} else if (!canRemoveWholeContent) { } else if (!canRemoveWholeContent) {
remove(prevVNode, container) removeVNode(prevVNode, container)
} }
} }
} else { } else {
@ -995,7 +929,7 @@ export function createRenderer(options: RendererOptions) {
if (canRemoveWholeContent) { if (canRemoveWholeContent) {
canRemoveWholeContent = false canRemoveWholeContent = false
while (i > prevStart) { while (i > prevStart) {
remove(prevChildren[prevStart++], container) removeVNode(prevChildren[prevStart++], container)
} }
} }
nextVNode = nextChildren[j] nextVNode = nextChildren[j]
@ -1011,16 +945,16 @@ export function createRenderer(options: RendererOptions) {
patch(prevVNode, nextVNode, container, parentComponent, isSVG) patch(prevVNode, nextVNode, container, parentComponent, isSVG)
patched++ patched++
} else if (!canRemoveWholeContent) { } else if (!canRemoveWholeContent) {
remove(prevVNode, container) removeVNode(prevVNode, container)
} }
} else if (!canRemoveWholeContent) { } else if (!canRemoveWholeContent) {
remove(prevVNode, container) removeVNode(prevVNode, container)
} }
} }
} }
// fast-path: if nothing patched remove all old and add all new // fast-path: if nothing patched remove all old and add all new
if (canRemoveWholeContent) { if (canRemoveWholeContent) {
removeAll(prevChildren as MountedVNodes, container, endNode) removeChildren(prevChildren as MountedVNode[], container, endNode)
mountArrayChildren( mountArrayChildren(
nextChildren, nextChildren,
container, container,
@ -1051,9 +985,9 @@ export function createRenderer(options: RendererOptions) {
pos = i + nextStart pos = i + nextStart
nextVNode = nextChildren[pos] nextVNode = nextChildren[pos]
nextPos = pos + 1 nextPos = pos + 1
insertOrAppend( moveVNode(
nextVNode as MountedVNode,
container, container,
nextVNode.el as RenderNode | RenderFragment,
nextPos < nextLength ? nextChildren[nextPos].el : endNode nextPos < nextLength ? nextChildren[nextPos].el : endNode
) )
} else { } else {
@ -1085,9 +1019,32 @@ export function createRenderer(options: RendererOptions) {
} }
} }
function moveVNode(
vnode: MountedVNode,
container: RenderNode,
refNode: RenderNode | null
) {
const { flags, childFlags, children } = vnode
if (flags & VNodeFlags.FRAGMENT) {
switch (childFlags) {
case ChildrenFlags.SINGLE_VNODE:
moveVNode(children as MountedVNode, container, refNode)
break
case ChildrenFlags.NO_CHILDREN:
break
default:
for (let i = 0; i < (children as MountedVNode[]).length; i++) {
moveVNode((children as MountedVNode[])[i], container, refNode)
}
}
} else {
insertOrAppend(container, vnode.el as RenderNode, refNode)
}
}
// unmounting ---------------------------------------------------------------- // unmounting ----------------------------------------------------------------
function unmount(vnode: VNode) { function unmount(vnode: MountedVNode) {
const { flags, data, children, childFlags, ref } = vnode const { flags, data, children, childFlags, ref } = vnode
const isElement = flags & VNodeFlags.ELEMENT const isElement = flags & VNodeFlags.ELEMENT
if (isElement || flags & VNodeFlags.FRAGMENT) { if (isElement || flags & VNodeFlags.FRAGMENT) {
@ -1109,13 +1066,17 @@ export function createRenderer(options: RendererOptions) {
unmountComponentInstance(children as MountedComponent) unmountComponentInstance(children as MountedComponent)
} }
} else { } else {
unmount(children as VNode) unmount(children as MountedVNode)
} }
} else if (flags & VNodeFlags.PORTAL) { } else if (flags & VNodeFlags.PORTAL) {
if (childFlags & ChildrenFlags.MULTIPLE_VNODES) { if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {
removeAll(children as MountedVNodes, vnode.tag as RenderNode, null) removeChildren(
children as MountedVNode[],
vnode.tag as RenderNode,
null
)
} else if (childFlags === ChildrenFlags.SINGLE_VNODE) { } else if (childFlags === ChildrenFlags.SINGLE_VNODE) {
remove(children as VNode, vnode.tag as RenderNode) removeVNode(children as MountedVNode, vnode.tag as RenderNode)
} }
} }
if (ref) { if (ref) {
@ -1125,37 +1086,53 @@ export function createRenderer(options: RendererOptions) {
function unmountChildren(children: VNodeChildren, childFlags: ChildrenFlags) { function unmountChildren(children: VNodeChildren, childFlags: ChildrenFlags) {
if (childFlags & ChildrenFlags.MULTIPLE_VNODES) { if (childFlags & ChildrenFlags.MULTIPLE_VNODES) {
unmountArrayChildren(children as VNode[]) unmountArrayChildren(children as MountedVNode[])
} else if (childFlags === ChildrenFlags.SINGLE_VNODE) { } else if (childFlags === ChildrenFlags.SINGLE_VNODE) {
unmount(children as VNode) unmount(children as MountedVNode)
} }
} }
function unmountArrayChildren(children: VNode[]) { function unmountArrayChildren(children: MountedVNode[]) {
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
unmount(children[i]) unmount(children[i])
} }
} }
function remove(vnode: VNode, container: RenderNode) { function removeVNode(vnode: MountedVNode, container: RenderNode) {
unmount(vnode) unmount(vnode)
if (container && vnode.el) { const { el, flags, children, childFlags } = vnode
removeChild(container, vnode.el) if (container && el) {
vnode.el = null if (flags & VNodeFlags.FRAGMENT) {
switch (childFlags) {
case ChildrenFlags.SINGLE_VNODE:
removeVNode(children as MountedVNode, container)
break
case ChildrenFlags.NO_CHILDREN:
platformRemoveChild(container, el)
break
default:
for (let i = 0; i < (children as MountedVNode[]).length; i++) {
removeVNode((children as MountedVNode[])[i], container)
}
}
} else {
platformRemoveChild(container, el)
}
;(vnode as any).el = null
} }
} }
function removeAll( function removeChildren(
children: MountedVNodes, children: MountedVNode[],
container: RenderNode, container: RenderNode,
ref: RenderNode | RenderFragment | null refNode: RenderNode | null
) { ) {
unmountArrayChildren(children) unmountArrayChildren(children)
if (ref === null) { if (refNode === null) {
platformClearContent(container) platformClearContent(container)
} else { } else {
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
removeChild(container, children[i].el as RenderNode | RenderFragment) removeVNode(children[i], container)
} }
} }
} }
@ -1168,7 +1145,7 @@ export function createRenderer(options: RendererOptions) {
container: RenderNode | null, container: RenderNode | null,
parentComponent: MountedComponent | null, parentComponent: MountedComponent | null,
isSVG: boolean, isSVG: boolean,
endNode: RenderNode | RenderFragment | 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()
@ -1195,10 +1172,10 @@ export function createRenderer(options: RendererOptions) {
return return
} }
if (instance._mounted) { if (instance._mounted) {
updateComponentInstance(instance, container, isSVG) updateComponentInstance(instance, isSVG)
} else { } else {
// this will be executed synchronously right here // this will be executed synchronously right here
instance.$vnode = renderInstanceRoot(instance) instance.$vnode = renderInstanceRoot(instance) as MountedVNode
mount(instance.$vnode, container, instance, isSVG, endNode) mount(instance.$vnode, container, instance, isSVG, endNode)
parentVNode.el = instance.$vnode.el parentVNode.el = instance.$vnode.el
instance._mounted = true instance._mounted = true
@ -1229,20 +1206,17 @@ export function createRenderer(options: RendererOptions) {
} }
} }
function updateComponentInstance( function updateComponentInstance(instance: MountedComponent, isSVG: boolean) {
instance: MountedComponent,
container: RenderNode | null,
isSVG: boolean
) {
const prevVNode = instance.$vnode const prevVNode = instance.$vnode
if (instance.beforeUpdate) { if (instance.beforeUpdate) {
instance.beforeUpdate.call(instance.$proxy, prevVNode) instance.beforeUpdate.call(instance.$proxy, prevVNode)
} }
const nextVNode = (instance.$vnode = renderInstanceRoot(instance)) const nextVNode = (instance.$vnode = renderInstanceRoot(
container = instance
container || parentNode(prevVNode.el as RenderNode | RenderFragment) ) as MountedVNode)
const container = platformParentNode(prevVNode.el) as RenderNode
patch(prevVNode, nextVNode, container, instance, isSVG) patch(prevVNode, nextVNode, container, instance, isSVG)
const el = nextVNode.el as RenderNode const el = nextVNode.el as RenderNode
@ -1299,13 +1273,12 @@ export function createRenderer(options: RendererOptions) {
// Keep Alive ---------------------------------------------------------------- // Keep Alive ----------------------------------------------------------------
function activateComponentInstance(vnode: VNode): RenderNode { function activateComponentInstance(vnode: VNode) {
const instance = vnode.children as MountedComponent const instance = vnode.children as MountedComponent
const el = (vnode.el = instance.$el) vnode.el = instance.$el
lifecycleHooks.push(() => { lifecycleHooks.push(() => {
callActivatedHook(instance, true) callActivatedHook(instance, true)
}) })
return el as RenderNode
} }
function callActivatedHook(instance: MountedComponent, asRoot: boolean) { function callActivatedHook(instance: MountedComponent, asRoot: boolean) {
@ -1373,7 +1346,7 @@ export function createRenderer(options: RendererOptions) {
patch(prevVNode, vnode, container, null, false) patch(prevVNode, vnode, container, null, false)
container.vnode = vnode container.vnode = vnode
} else { } else {
remove(prevVNode, container) removeVNode(prevVNode, container)
container.vnode = null container.vnode = null
} }
} }

View File

@ -15,11 +15,6 @@ export interface RenderNode {
$f: false $f: false
} }
export interface RenderFragment {
children: (RenderNode | RenderFragment)[]
$f: true
}
export interface VNode { export interface VNode {
_isVNode: true _isVNode: true
flags: VNodeFlags flags: VNodeFlags
@ -31,18 +26,16 @@ export interface VNode {
ref: Ref | null ref: Ref | null
slots: Slots | null slots: Slots | null
// only on mounted nodes // only on mounted nodes
el: RenderNode | RenderFragment | null el: RenderNode | null
// only on mounted component root nodes // only on mounted component root nodes
// points to component node in parent tree // points to component node in parent tree
parentVNode: VNode | null parentVNode: VNode | null
} }
export interface MountedVNode extends VNode { export interface MountedVNode extends VNode {
el: RenderNode | RenderFragment el: RenderNode
} }
export type MountedVNodes = MountedVNode[]
export interface VNodeData { export interface VNodeData {
key?: Key | null key?: Key | null
ref?: Ref | null ref?: Ref | null