wip: diffKeyedChildren

This commit is contained in:
Evan You 2019-05-27 13:48:40 +08:00
parent 444d6f4bda
commit e4ce78c8c9
3 changed files with 101 additions and 23 deletions

View File

@ -70,7 +70,7 @@ export function createRenderer(options: RendererOptions) {
) { ) {
// patching & not same type, unmount old tree // patching & not same type, unmount old tree
if (n1 != null && !isSameType(n1, n2)) { if (n1 != null && !isSameType(n1, n2)) {
anchor = hostNextSibling(n1.el) anchor = hostNextSibling(n1.anchor || n1.el)
unmount(n1, true) unmount(n1, true)
n1 = null n1 = null
} }
@ -304,13 +304,17 @@ export function createRenderer(options: RendererOptions) {
anchor?: HostNode, anchor?: HostNode,
optimized?: boolean optimized?: boolean
) { ) {
const fragmentAnchor = (n2.el = n1 ? n1.el : document.createComment('')) const fragmentStartAnchor = (n2.el = n1 ? n1.el : hostCreateComment(''))
const fragmentEndAnchor = (n2.anchor = n1
? n1.anchor
: hostCreateComment(''))
if (n1 == null) { if (n1 == null) {
insert(fragmentAnchor, container, anchor) insert(fragmentStartAnchor, container, anchor)
insert(fragmentEndAnchor, container, anchor)
// a fragment can only have array children // a fragment can only have array children
mountChildren(n2.children as VNodeChildren, container, fragmentAnchor) mountChildren(n2.children as VNodeChildren, container, fragmentEndAnchor)
} else { } else {
patchChildren(n1, n2, container, fragmentAnchor, optimized) patchChildren(n1, n2, container, fragmentEndAnchor, optimized)
} }
} }
@ -369,7 +373,7 @@ export function createRenderer(options: RendererOptions) {
patchKeyedChildren(c1 as VNode[], c2, container, anchor, optimized) patchKeyedChildren(c1 as VNode[], c2, container, anchor, optimized)
} else { } else {
// c2 is null in this case // c2 is null in this case
unmountChildren(c1 as VNode[], 0, true) unmountChildren(c1 as VNode[], true)
} }
} }
} }
@ -394,7 +398,7 @@ export function createRenderer(options: RendererOptions) {
} }
if (oldLength > newLength) { if (oldLength > newLength) {
// remove old // remove old
unmountChildren(c1, commonLength, true) unmountChildren(c1, true, commonLength)
} else { } else {
// mount new // mount new
mountChildren(c2, container, anchor, commonLength) mountChildren(c2, container, anchor, commonLength)
@ -409,29 +413,98 @@ export function createRenderer(options: RendererOptions) {
anchor?: HostNode, anchor?: HostNode,
optimized?: boolean optimized?: boolean
) { ) {
// TODO let i = 0
patchUnkeyedChildren(c1, c2, container, anchor, optimized) let e1 = c1.length - 1
let e2 = c2.length - 1
// 1. sync from start
// (a b) c
// (a b) d e
while (i <= e1 && i <= e2) {
const n1 = c1[i]
const n2 = (c2[i] = normalizeChild(c2[i]))
if (isSameType(n1, n2)) {
patch(n1, n2, container, anchor, optimized)
} else {
break
}
i++
}
// 2. sync from end
// a (b c)
// d e (b c)
while (i <= e1 && i <= e2) {
const n1 = c1[e1]
const n2 = (c2[e2] = normalizeChild(c2[e2]))
if (isSameType(n1, n2)) {
patch(n1, n2, container, anchor, optimized)
} else {
break
}
e1--
e2--
}
// 3. common sequence + mount
// (a b)
// (a b) c
// i = 2, e1 = 1, e2 = 2
// (a b)
// c (a b)
// i = 0, e1 = -1, e2 = 0
if (i > e1) {
if (i <= e2) {
const nextPos = e2 + 1
const nextAnchor =
nextPos < c2.length ? (c2[nextPos] as VNode).el : anchor
while (i <= e2) {
patch(null, (c2[i] = normalizeChild(c2[i])), container, nextAnchor)
i++
}
}
}
// 4. common sequence + unmount
// (a b) c
// (a b)
// i = 2, e1 = 2, e2 = 1
// a (b c)
// (b c)
// i = 0, e1 = 0, e2 = -1
else if (i > e2) {
while (i <= e1) {
unmount(c1[i], true)
i++
}
}
// 5. inner diff
// - build key:index map
// - loop through old and try to patch / remove
// - if moved: apply minimal move w/ levenshtein distance
// - no move: mount and insert new ones
else {
}
} }
function unmount(vnode: VNode, doRemove?: boolean) { function unmount(vnode: VNode, doRemove?: boolean) {
const shouldRemoveChildren = vnode.type === Fragment && doRemove
if (vnode.dynamicChildren != null) { if (vnode.dynamicChildren != null) {
unmountChildren(vnode.dynamicChildren) unmountChildren(vnode.dynamicChildren, shouldRemoveChildren)
} else if (Array.isArray(vnode.children)) { } else if (Array.isArray(vnode.children)) {
unmountChildren(vnode.children as VNode[]) unmountChildren(vnode.children as VNode[], shouldRemoveChildren)
} }
if (doRemove) { if (doRemove) {
if (vnode.type === Fragment) {
// raw VNodeChildren is normalized to VNode[] when the VNode is patched
unmountChildren(vnode.children as VNode[], 0, doRemove)
}
remove(vnode.el) remove(vnode.el)
if (vnode.anchor != null) remove(vnode.anchor)
} }
} }
function unmountChildren( function unmountChildren(
children: VNode[], children: VNode[],
start: number = 0, doRemove?: boolean,
doRemove?: boolean start: number = 0
) { ) {
for (let i = start; i < children.length; i++) { for (let i = start; i < children.length; i++) {
unmount(children[i], doRemove) unmount(children[i], doRemove)

View File

@ -14,6 +14,7 @@ export interface VNodeChildren extends Array<VNodeChildren | VNodeChild> {}
export interface VNode { export interface VNode {
el: any el: any
anchor: any // fragment anchor
type: VNodeTypes type: VNodeTypes
props: { [key: string]: any } | null props: { [key: string]: any } | null
key: string | number | null key: string | number | null
@ -23,11 +24,11 @@ export interface VNode {
dynamicChildren: VNode[] | null dynamicChildren: VNode[] | null
} }
const blockStack: (VNode[])[] = [] const blockStack: (VNode[] | null)[] = []
// open block // open block
export function openBlock() { export function openBlock(disableTrackng?: boolean) {
blockStack.push([]) blockStack.push(disableTrackng ? null : [])
} }
let shouldTrack = true let shouldTrack = true
@ -44,7 +45,9 @@ export function createBlock(
shouldTrack = false shouldTrack = false
const vnode = createVNode(type, props, children, patchFlag, dynamicProps) const vnode = createVNode(type, props, children, patchFlag, dynamicProps)
shouldTrack = true shouldTrack = true
vnode.dynamicChildren = blockStack.pop() || null const trackedNodes = blockStack.pop()
vnode.dynamicChildren =
trackedNodes && trackedNodes.length ? trackedNodes : null
// a block is always going to be patched // a block is always going to be patched
trackDynamicNode(vnode) trackDynamicNode(vnode)
return vnode return vnode
@ -59,11 +62,12 @@ export function createVNode(
dynamicProps: string[] | null = null dynamicProps: string[] | null = null
): VNode { ): VNode {
const vnode: VNode = { const vnode: VNode = {
el: null,
type, type,
props, props,
key: props && props.key, key: props && props.key,
children, children,
el: null,
anchor: null,
patchFlag, patchFlag,
dynamicProps, dynamicProps,
dynamicChildren: null dynamicChildren: null
@ -76,7 +80,7 @@ export function createVNode(
function trackDynamicNode(vnode: VNode) { function trackDynamicNode(vnode: VNode) {
const currentBlockDynamicNodes = blockStack[blockStack.length - 1] const currentBlockDynamicNodes = blockStack[blockStack.length - 1]
if (currentBlockDynamicNodes) { if (currentBlockDynamicNodes != null) {
currentBlockDynamicNodes.push(vnode) currentBlockDynamicNodes.push(vnode)
} }
} }

View File

@ -15,6 +15,7 @@ export const DOMRendererOptions: RendererOptions = {
}, },
remove: (child: Node) => { remove: (child: Node) => {
if (!child) debugger
const parent = child.parentNode const parent = child.parentNode
if (parent != null) { if (parent != null) {
parent.removeChild(child) parent.removeChild(child)