From e4ce78c8c975150d5adadd4c47b0f25b8c652d24 Mon Sep 17 00:00:00 2001 From: Evan You Date: Mon, 27 May 2019 13:48:40 +0800 Subject: [PATCH] wip: diffKeyedChildren --- packages/runtime-core/src/createRenderer.ts | 107 ++++++++++++++++---- packages/runtime-core/src/h.ts | 16 +-- packages/runtime-dom/src/rendererOptions.ts | 1 + 3 files changed, 101 insertions(+), 23 deletions(-) diff --git a/packages/runtime-core/src/createRenderer.ts b/packages/runtime-core/src/createRenderer.ts index d700809d..9ca2bc21 100644 --- a/packages/runtime-core/src/createRenderer.ts +++ b/packages/runtime-core/src/createRenderer.ts @@ -70,7 +70,7 @@ export function createRenderer(options: RendererOptions) { ) { // patching & not same type, unmount old tree if (n1 != null && !isSameType(n1, n2)) { - anchor = hostNextSibling(n1.el) + anchor = hostNextSibling(n1.anchor || n1.el) unmount(n1, true) n1 = null } @@ -304,13 +304,17 @@ export function createRenderer(options: RendererOptions) { anchor?: HostNode, 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) { - insert(fragmentAnchor, container, anchor) + insert(fragmentStartAnchor, container, anchor) + insert(fragmentEndAnchor, container, anchor) // a fragment can only have array children - mountChildren(n2.children as VNodeChildren, container, fragmentAnchor) + mountChildren(n2.children as VNodeChildren, container, fragmentEndAnchor) } 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) } else { // 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) { // remove old - unmountChildren(c1, commonLength, true) + unmountChildren(c1, true, commonLength) } else { // mount new mountChildren(c2, container, anchor, commonLength) @@ -409,29 +413,98 @@ export function createRenderer(options: RendererOptions) { anchor?: HostNode, optimized?: boolean ) { - // TODO - patchUnkeyedChildren(c1, c2, container, anchor, optimized) + let i = 0 + 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) { + const shouldRemoveChildren = vnode.type === Fragment && doRemove if (vnode.dynamicChildren != null) { - unmountChildren(vnode.dynamicChildren) + unmountChildren(vnode.dynamicChildren, shouldRemoveChildren) } else if (Array.isArray(vnode.children)) { - unmountChildren(vnode.children as VNode[]) + unmountChildren(vnode.children as VNode[], shouldRemoveChildren) } 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) + if (vnode.anchor != null) remove(vnode.anchor) } } function unmountChildren( children: VNode[], - start: number = 0, - doRemove?: boolean + doRemove?: boolean, + start: number = 0 ) { for (let i = start; i < children.length; i++) { unmount(children[i], doRemove) diff --git a/packages/runtime-core/src/h.ts b/packages/runtime-core/src/h.ts index 3f3077a4..ef84138e 100644 --- a/packages/runtime-core/src/h.ts +++ b/packages/runtime-core/src/h.ts @@ -14,6 +14,7 @@ export interface VNodeChildren extends Array {} export interface VNode { el: any + anchor: any // fragment anchor type: VNodeTypes props: { [key: string]: any } | null key: string | number | null @@ -23,11 +24,11 @@ export interface VNode { dynamicChildren: VNode[] | null } -const blockStack: (VNode[])[] = [] +const blockStack: (VNode[] | null)[] = [] // open block -export function openBlock() { - blockStack.push([]) +export function openBlock(disableTrackng?: boolean) { + blockStack.push(disableTrackng ? null : []) } let shouldTrack = true @@ -44,7 +45,9 @@ export function createBlock( shouldTrack = false const vnode = createVNode(type, props, children, patchFlag, dynamicProps) 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 trackDynamicNode(vnode) return vnode @@ -59,11 +62,12 @@ export function createVNode( dynamicProps: string[] | null = null ): VNode { const vnode: VNode = { - el: null, type, props, key: props && props.key, children, + el: null, + anchor: null, patchFlag, dynamicProps, dynamicChildren: null @@ -76,7 +80,7 @@ export function createVNode( function trackDynamicNode(vnode: VNode) { const currentBlockDynamicNodes = blockStack[blockStack.length - 1] - if (currentBlockDynamicNodes) { + if (currentBlockDynamicNodes != null) { currentBlockDynamicNodes.push(vnode) } } diff --git a/packages/runtime-dom/src/rendererOptions.ts b/packages/runtime-dom/src/rendererOptions.ts index 05757a9c..a1c5b3dd 100644 --- a/packages/runtime-dom/src/rendererOptions.ts +++ b/packages/runtime-dom/src/rendererOptions.ts @@ -15,6 +15,7 @@ export const DOMRendererOptions: RendererOptions = { }, remove: (child: Node) => { + if (!child) debugger const parent = child.parentNode if (parent != null) { parent.removeChild(child)