From ed2f6e8df5a2c992186ab77cc127bb52dceeab59 Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 1 Nov 2022 20:12:47 +0800 Subject: [PATCH 1/4] Match-id-3d913d35f0eb68f4d311fcf073625132167ca7b4 --- libs/horizon/src/renderer/TreeBuilder.ts | 20 +++--- .../src/renderer/diff/nodeDiffComparator.ts | 65 ++++++++----------- 2 files changed, 39 insertions(+), 46 deletions(-) diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts index 71dd01e0..4e1e864d 100644 --- a/libs/horizon/src/renderer/TreeBuilder.ts +++ b/libs/horizon/src/renderer/TreeBuilder.ts @@ -86,7 +86,7 @@ function collectDirtyNodes(vNode: VNode, parent: VNode): void { if (parent.dirtyNodes === null) { parent.dirtyNodes = dirtyNodes; } else { - parent.dirtyNodes.push(...vNode.dirtyNodes); + parent.dirtyNodes.push(...dirtyNodes); dirtyNodes.length = 0; } vNode.dirtyNodes = null; @@ -105,7 +105,7 @@ function collectDirtyNodes(vNode: VNode, parent: VNode): void { // 尝试完成当前工作单元,然后移动到下一个兄弟工作单元。如果没有更多的同级,请返回父vNode。 function bubbleVNode(vNode: VNode): void { - let node = vNode; + let node : VNode | null = vNode; do { const parent = node.parent; @@ -182,14 +182,18 @@ function isEqualByIndex(idx: number, pathArrays: string[][]) { function getChildByIndex(vNode: VNode, idx: number) { let node = vNode.child; for (let i = 0; i < idx; i++) { - node = node.next; + if (node) { + node = node.next; + } else { + return null; + } } return node; } // 从多个更新节点中,计算出开始节点。即:找到最近的共同的父辈节点 export function calcStartUpdateVNode(treeRoot: VNode) { - const toUpdateNodes = Array.from(treeRoot.toUpdateNodes); + const toUpdateNodes = Array.from(treeRoot.toUpdateNodes!); if (toUpdateNodes.length === 0) { return treeRoot; @@ -218,12 +222,12 @@ export function calcStartUpdateVNode(treeRoot: VNode) { // 得到相等的路径 const startNodePath = pathArrays[0].slice(0, commonPathEndIndex); - let node = treeRoot; + let node: VNode | null = treeRoot; for (let i = 1; i < startNodePath.length; i++) { const pathIndex = Number(startNodePath[i]); - node = getChildByIndex(node, pathIndex)!; + node = getChildByIndex(node, pathIndex); // 路径错误时,回退到从根更新 - if (node == null) { + if (node === null) { return treeRoot; } } @@ -242,7 +246,7 @@ function buildVNodeTree(treeRoot: VNode) { setStartVNode(startVNode); // 清空toUpdateNodes - treeRoot.toUpdateNodes.clear(); + treeRoot.toUpdateNodes!.clear(); if (startVNode.tag !== TreeRoot) { // 不是根节点 // 设置namespace,用于createElement diff --git a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts index 3899dae9..4186d361 100644 --- a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts +++ b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts @@ -24,13 +24,7 @@ import { createPortalVNode, createDomTextVNode, } from '../vnode/VNodeCreator'; -import { - isSameType, - getIteratorFn, - isTextType, - isIteratorType, - isObjectType, -} from './DiffTools'; +import { isSameType, getIteratorFn, isTextType, isIteratorType, isObjectType } from './DiffTools'; import { travelChildren } from '../vnode/VNodeUtils'; import { markVNodePath } from '../utils/vNodePath'; @@ -120,10 +114,12 @@ function getNodeType(newChild: any): string | null { function setVNodeAdditionFlag(newNode: VNode, lastPosition: number): number { let position = lastPosition; - if (newNode.isCreated || newNode.eIndex < lastPosition) { // 位置 小于 上一个复用的位置 + if (newNode.isCreated || newNode.eIndex < lastPosition) { + // 位置 小于 上一个复用的位置 // 标记为新增 FlagUtils.setAddition(newNode); - } else { // 复用 + } else { + // 复用 position = newNode.eIndex; } @@ -206,15 +202,16 @@ function transRightChildrenToArray(child) { return rightChildrenArray; } -function transLeftChildrenToMap( - startChild: VNode, - rightEndVNode: VNode | null, -): Map { +function transLeftChildrenToMap(startChild: VNode, rightEndVNode: VNode | null): Map { const leftChildrenMap: Map = new Map(); - travelChildren(startChild, node => { - leftChildrenMap.set(node.key !== null ? node.key : node.eIndex, node); - }, node => node === rightEndVNode); + travelChildren( + startChild, + node => { + leftChildrenMap.set(node.key !== null ? node.key : node.eIndex, node); + }, + node => node === rightEndVNode + ); return leftChildrenMap; } @@ -235,11 +232,7 @@ function getOldNodeFromMap(nodeMap: Map, newIdx: number, } // diff数组类型的节点,核心算法 -function diffArrayNodesHandler( - parentNode: VNode, - firstChild: VNode | null, - newChildren: Array, -): VNode | null { +function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newChildren: Array): VNode | null { let resultingFirstChild: VNode | null = null; let prevNewNode: VNode | null = null; @@ -358,7 +351,7 @@ function diffArrayNodesHandler( if (rightNewNode) { appendNode(rightNewNode); - setVNodesCIndex(rightNewNode, prevNewNode.cIndex + 1); + setVNodesCIndex(rightNewNode, rightNewNode.cIndex + 1); } return resultingFirstChild; @@ -366,13 +359,14 @@ function diffArrayNodesHandler( // 4. 新节点还有一部分,但是老节点已经没有了 if (oldNode === null) { - let isDirectAdd = false; // TODO: 是否可以扩大至非dom类型节点 // 如果dom节点在上次添加前没有节点,说明本次添加时,可以直接添加到最后,不需要通过 getSiblingDom 函数找到 before 节点 - if (parentNode.tag === DomComponent && + if ( + parentNode.tag === DomComponent && parentNode.oldProps?.children?.length === 0 && - rightIdx - leftIdx === newChildren.length) { + rightIdx - leftIdx === newChildren.length + ) { isDirectAdd = true; } const isAddition = parentNode.tag === DomPortal || !parentNode.isCreated; @@ -424,7 +418,8 @@ function diffArrayNodesHandler( const eIndex = newNode.eIndex; eIndexes.push(eIndex); last = eIndexes[result[result.length - 1]]; - if (eIndex > last || last === undefined) { // 大的 eIndex直接放在最后 + if (eIndex > last || last === undefined) { + // 大的 eIndex直接放在最后 preIndex[i] = result[result.length - 1]; result.push(i); } else { @@ -500,13 +495,13 @@ function setVNodesCIndex(startChild: VNode | null, startIdx: number) { function diffIteratorNodesHandler( parentNode: VNode, firstChild: VNode | null, - newChildrenIterable: Iterable, + newChildrenIterable: Iterable ): VNode | null { const iteratorFn = getIteratorFn(newChildrenIterable); - const iteratorObj = iteratorFn.call(newChildrenIterable); + const iteratorObj: Iterator = iteratorFn.call(newChildrenIterable); // 把iterator转测数组 - const childrenArray = []; + const childrenArray: any[] = []; let result = iteratorObj.next(); while (!result.done) { childrenArray.push(result.value); @@ -517,12 +512,7 @@ function diffIteratorNodesHandler( } // 新节点是字符串类型 -function diffStringNodeHandler( - parentNode: VNode, - newChild: any, - firstChildVNode: VNode, - isComparing: boolean -) { +function diffStringNodeHandler(parentNode: VNode, newChild: any, firstChildVNode: VNode | null, isComparing: boolean) { let newTextNode: VNode | null = null; // 第一个vNode是Text,则复用 @@ -550,7 +540,6 @@ function diffObjectNodeHandler( parentNode: VNode, firstChild: VNode | null, newChild: any, - firstChildVNode: VNode, isComparing: boolean ) { let canReuseNode: VNode | null = null; @@ -569,7 +558,7 @@ function diffObjectNodeHandler( } let resultNode: VNode | null = null; - let startDelVNode = firstChildVNode; + let startDelVNode: VNode | null = firstChild; if (newChild.vtype === TYPE_COMMON_ELEMENT) { if (canReuseNode) { // 可以复用 @@ -664,7 +653,7 @@ export function createChildrenByDiff( // 5. newChild是对象类型 if (isObjectType(newChild)) { - const newVNodes = diffObjectNodeHandler(parentNode, firstChild, newChild, firstChild, isComparing); + const newVNodes = diffObjectNodeHandler(parentNode, firstChild, newChild, isComparing); if (newVNodes) { return newVNodes; } From a39db5b6f9c59a96222a3c174f934ea40fb83878 Mon Sep 17 00:00:00 2001 From: * <8> Date: Tue, 1 Nov 2022 20:38:46 +0800 Subject: [PATCH 2/4] Match-id-b6f9fc4a755903e35420b220d2b1dc6edd52fe24 --- .../horizon/src/renderer/diff/nodeDiffComparator.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts index 56753063..4186d361 100644 --- a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts +++ b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts @@ -351,7 +351,7 @@ function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newC if (rightNewNode) { appendNode(rightNewNode); - setVNodesCIndex(rightNewNode, prevNewNode.cIndex + 1); + setVNodesCIndex(rightNewNode, rightNewNode.cIndex + 1); } return resultingFirstChild; @@ -498,10 +498,10 @@ function diffIteratorNodesHandler( newChildrenIterable: Iterable ): VNode | null { const iteratorFn = getIteratorFn(newChildrenIterable); - const iteratorObj = iteratorFn.call(newChildrenIterable); + const iteratorObj: Iterator = iteratorFn.call(newChildrenIterable); // 把iterator转测数组 - const childrenArray = []; + const childrenArray: any[] = []; let result = iteratorObj.next(); while (!result.done) { childrenArray.push(result.value); @@ -512,7 +512,7 @@ function diffIteratorNodesHandler( } // 新节点是字符串类型 -function diffStringNodeHandler(parentNode: VNode, newChild: any, firstChildVNode: VNode, isComparing: boolean) { +function diffStringNodeHandler(parentNode: VNode, newChild: any, firstChildVNode: VNode | null, isComparing: boolean) { let newTextNode: VNode | null = null; // 第一个vNode是Text,则复用 @@ -540,7 +540,6 @@ function diffObjectNodeHandler( parentNode: VNode, firstChild: VNode | null, newChild: any, - firstChildVNode: VNode, isComparing: boolean ) { let canReuseNode: VNode | null = null; @@ -559,7 +558,7 @@ function diffObjectNodeHandler( } let resultNode: VNode | null = null; - let startDelVNode = firstChildVNode; + let startDelVNode: VNode | null = firstChild; if (newChild.vtype === TYPE_COMMON_ELEMENT) { if (canReuseNode) { // 可以复用 @@ -654,7 +653,7 @@ export function createChildrenByDiff( // 5. newChild是对象类型 if (isObjectType(newChild)) { - const newVNodes = diffObjectNodeHandler(parentNode, firstChild, newChild, firstChild, isComparing); + const newVNodes = diffObjectNodeHandler(parentNode, firstChild, newChild, isComparing); if (newVNodes) { return newVNodes; } From 88b13ffaa4efa38f22ad823584f982661164e8b0 Mon Sep 17 00:00:00 2001 From: * <8> Date: Mon, 7 Nov 2022 10:21:25 +0800 Subject: [PATCH 3/4] Match-id-1dba96d1d4a0290164da813181e61c44591c21d0 --- libs/horizon/src/renderer/TreeBuilder.ts | 35 ++- .../src/renderer/diff/nodeDiffComparator.ts | 255 +++++++++++------- 2 files changed, 175 insertions(+), 115 deletions(-) diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts index b85a6930..622475be 100644 --- a/libs/horizon/src/renderer/TreeBuilder.ts +++ b/libs/horizon/src/renderer/TreeBuilder.ts @@ -100,7 +100,7 @@ function collectDirtyNodes(vNode: VNode, parent: VNode): void { // 尝试完成当前工作单元,然后移动到下一个兄弟工作单元。如果没有更多的同级,请返回父vNode。 function bubbleVNode(vNode: VNode): void { - let node : VNode | null = vNode; + let node: VNode | null = vNode; do { const parent = node.parent; @@ -351,16 +351,10 @@ function renderFromRoot(treeRoot) { // 2. 提交变更 submitToRender(treeRoot); popCurrentRoot(); - if (window.__HORIZON_DEV_HOOK__) { - const hook = window.__HORIZON_DEV_HOOK__; - // injector.js 可能在 Horizon 代码之后加载,此时无 __HORIZON_DEV_HOOK__ 全局变量 - // Horizon 代码初次加载时不会初始化 helper - if (!hook.isInit) { - injectUpdater(); - } - hook.addIfNotInclude(treeRoot); - hook.send(treeRoot); - } + + // 与Devtool通信 + sendRootToDevTool(treeRoot); + return null; } @@ -407,6 +401,25 @@ export function launchUpdateFromVNode(vNode: VNode) { } } +declare global { + interface Window { + __HORIZON_DEV_HOOK__: any; + } +} + +function sendRootToDevTool(treeRoot) { + if (window.__HORIZON_DEV_HOOK__) { + const hook = window.__HORIZON_DEV_HOOK__; + // injector.js 可能在 Horizon 代码之后加载,此时无 __HORIZON_DEV_HOOK__ 全局变量 + // Horizon 代码初次加载时不会初始化 helper + if (!hook.isInit) { + injectUpdater(); + } + hook.addIfNotInclude(treeRoot); + hook.send(treeRoot); + } +} + // ============================== HorizonDOM使用 ============================== export function runDiscreteUpdates() { if (checkMode(ByAsync) || checkMode(InRender)) { diff --git a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts index 4186d361..a66a627a 100644 --- a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts +++ b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts @@ -34,6 +34,13 @@ enum DiffCategory { ARR_NODE = 'ARR_NODE', } +type AppendVNode = (vnode: VNode) => void; +interface DiffRightSideResult { + rightIdx: number; + rightEndOldNode: VNode; + rightNewNode: VNode | null; +} + // 检查是不是被 FRAGMENT 包裹 function isNoKeyFragment(child: any) { return child != null && child.type === TYPE_FRAGMENT && child.key === null; @@ -111,19 +118,12 @@ function getNodeType(newChild: any): string | null { } // 设置vNode的flag -function setVNodeAdditionFlag(newNode: VNode, lastPosition: number): number { - let position = lastPosition; - - if (newNode.isCreated || newNode.eIndex < lastPosition) { +function setVNodeAdditionFlag(newNode: VNode) { + if (newNode.isCreated) { // 位置 小于 上一个复用的位置 // 标记为新增 FlagUtils.setAddition(newNode); - } else { - // 复用 - position = newNode.eIndex; } - - return position; } // 获取新节点 @@ -231,33 +231,10 @@ function getOldNodeFromMap(nodeMap: Map, newIdx: number, return null; } -// diff数组类型的节点,核心算法 -function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newChildren: Array): VNode | null { - let resultingFirstChild: VNode | null = null; - - let prevNewNode: VNode | null = null; - - let oldNode = firstChild; +function diffLeftSide(oldNode: VNode | null, newChildren: Array, parentNode: VNode, appendNode: AppendVNode) { let nextOldNode: VNode | null = null; - - let theLastPosition = 0; // 从左边开始的位置 let leftIdx = 0; - - function appendNode(newNode: VNode) { - if (prevNewNode === null) { - resultingFirstChild = newNode; - newNode.cIndex = 0; - } else { - prevNewNode.next = newNode; - newNode.cIndex = prevNewNode.cIndex + 1; - } - markVNodePath(newNode); - prevNewNode = newNode; - } - - let canBeReuse; - let newNode; // 1. 从左侧开始比对currentVNode和newChildren,若不能复用则跳出循环 for (; oldNode !== null && leftIdx < newChildren.length; leftIdx++) { if (oldNode.eIndex > leftIdx) { @@ -268,14 +245,13 @@ function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newC nextOldNode = oldNode.next; } - canBeReuse = checkCanReuseNode(oldNode, newChildren[leftIdx], leftIdx); // 不能复用,break - if (!canBeReuse) { + if (!checkCanReuseNode(oldNode, newChildren[leftIdx], leftIdx)) { oldNode = oldNode ?? nextOldNode; break; } - newNode = getNewNode(parentNode, newChildren[leftIdx], oldNode); + const newNode = getNewNode(parentNode, newChildren[leftIdx], oldNode); // 没有生成新节点,break if (!newNode) { oldNode = oldNode ?? nextOldNode; @@ -287,12 +263,23 @@ function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newC deleteVNode(parentNode, oldNode); } - theLastPosition = setVNodeAdditionFlag(newNode, theLastPosition); + setVNodeAdditionFlag(newNode); newNode.eIndex = leftIdx; appendNode(newNode); oldNode = nextOldNode; } + return { oldNode, leftIdx }; +} + +function diffRightSide( + oldNode: VNode | null, + leftIdx: number, + newChildren: Array, + parentNode: VNode +): DiffRightSideResult { + let newNode; + let canBeReuse = false; let rightIdx = newChildren.length; let rightEndOldNode; // 老节点中最右边匹配的节点引用 abcde --> abfde 则rightEndOldNode = c; let rightNewNode: VNode | null = null; // 最右边匹配的节点引用 abcde --> abfde 则rightNewNode = d; @@ -333,72 +320,101 @@ function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newC deleteVNode(parentNode, rightOldNode); } - setVNodeAdditionFlag(newNode, theLastPosition); + setVNodeAdditionFlag(newNode); newNode.eIndex = rightIdx - 1; rightOldIndex--; rightEndOldNode = rightOldNode; } } - // 3. 新节点已经处理完成 - if (leftIdx === rightIdx) { - if (firstChild && parentNode.tag === DomComponent && newChildren.length === 0) { - FlagUtils.markClear(parentNode); - parentNode.clearChild = firstChild; - } else { - deleteVNodes(parentNode, oldNode, rightEndOldNode); - } + return { rightIdx, rightEndOldNode, rightNewNode }; +} - if (rightNewNode) { - appendNode(rightNewNode); - setVNodesCIndex(rightNewNode, rightNewNode.cIndex + 1); - } +function appendRightSideNode( + oldNode: VNode | null, + parentNode: VNode, + firstChild: VNode | null, + newChildren: Array, + diffRightSideResult: DiffRightSideResult, + appendNode: AppendVNode +) { + const { rightEndOldNode, rightNewNode } = diffRightSideResult; - return resultingFirstChild; + if (firstChild && parentNode.tag === DomComponent && newChildren.length === 0) { + FlagUtils.markClear(parentNode); + parentNode.clearChild = firstChild; + } else { + deleteVNodes(parentNode, oldNode, rightEndOldNode); } - // 4. 新节点还有一部分,但是老节点已经没有了 - if (oldNode === null) { - let isDirectAdd = false; - // TODO: 是否可以扩大至非dom类型节点 - // 如果dom节点在上次添加前没有节点,说明本次添加时,可以直接添加到最后,不需要通过 getSiblingDom 函数找到 before 节点 - if ( - parentNode.tag === DomComponent && - parentNode.oldProps?.children?.length === 0 && - rightIdx - leftIdx === newChildren.length - ) { - isDirectAdd = true; - } - const isAddition = parentNode.tag === DomPortal || !parentNode.isCreated; - for (; leftIdx < rightIdx; leftIdx++) { - newNode = getNewNode(parentNode, newChildren[leftIdx], null); + if (rightNewNode) { + appendNode(rightNewNode); + setVNodesCIndex(rightNewNode, rightNewNode.cIndex + 1); + } +} - if (newNode !== null) { - if (isAddition) { - FlagUtils.setAddition(newNode); - } - if (isDirectAdd) { - FlagUtils.markDirectAddition(newNode); - } - newNode.eIndex = leftIdx; - appendNode(newNode); +function appendAllRestNode( + oldNode: VNode | null, + parentNode: VNode, + firstChild: VNode | null, + newChildren: Array, + leftIdx: number, + diffRightSideResult: DiffRightSideResult, + appendNode: AppendVNode +) { + let isDirectAdd = false; + const { rightIdx, rightNewNode } = diffRightSideResult; + + // 如果dom节点在上次添加前没有节点,说明本次添加时,可以直接添加到最后,不需要通过 getSiblingDom 函数找到 before 节点 + if ( + parentNode.tag === DomComponent && + parentNode.oldProps?.children?.length === 0 && + rightIdx - leftIdx === newChildren.length + ) { + isDirectAdd = true; + } + const isAddition = parentNode.tag === DomPortal || !parentNode.isCreated; + for (; leftIdx < rightIdx; leftIdx++) { + const newNode = getNewNode(parentNode, newChildren[leftIdx], null); + + if (newNode !== null) { + if (isAddition) { + FlagUtils.setAddition(newNode); } + if (isDirectAdd) { + FlagUtils.markDirectAddition(newNode); + } + newNode.eIndex = leftIdx; + appendNode(newNode); } - - if (rightNewNode) { - appendNode(rightNewNode); - setVNodesCIndex(rightNewNode.next, rightNewNode.cIndex + 1); - } - - return resultingFirstChild; } - // 5. 新节点还有一部分,但是老节点也还有一部分 + if (rightNewNode) { + appendNode(rightNewNode); + setVNodesCIndex(rightNewNode.next, rightNewNode.cIndex + 1); + } +} + +/** + * 双端对比完成,新老节点都有剩余时,构造LIS(最长递增子序列) + * 属于LIS的新节点直接复用,否则新增 + */ +function appendNodeWithLIS( + oldNode: VNode, + parentNode: VNode, + firstChild: VNode | null, + newChildren: Array, + leftIdx: number, + diffRightSideResult: DiffRightSideResult, + appendNode: AppendVNode +) { + const { rightIdx, rightNewNode, rightEndOldNode } = diffRightSideResult; + // 把剩下的currentVNode转成Map const leftChildrenMap = transLeftChildrenToMap(oldNode, rightEndOldNode); // 通过贪心算法+二分法获取最长递增子序列 const eIndexes: Array = []; // 记录 eIndex 值 - const result: Array = []; // 记录最长子序列在eIndexes中的 index 值 + const subsequence: Array = []; // 记录最长子序列在eIndexes中的 index 值 const preIndex: Array = []; // 贪心算法在替换的过程中会使得数组不正确,通过记录preIndex找到正确值 const reuseNodes: (VNode | null)[] = []; // 记录复用的 VNode let i = 0; @@ -406,7 +422,7 @@ function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newC let last; for (; leftIdx < rightIdx; leftIdx++) { oldNodeFromMap = getOldNodeFromMap(leftChildrenMap, leftIdx, newChildren[leftIdx]); - newNode = getNewNode(parentNode, newChildren[leftIdx], oldNodeFromMap); + const newNode = getNewNode(parentNode, newChildren[leftIdx], oldNodeFromMap); if (newNode !== null) { if (newNode.isCreated) { // 新VNode,直接打上标签新增,不参与到复用,旧的VNode会在后面打上delete标签 @@ -417,27 +433,27 @@ function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newC if (oldNodeFromMap !== null) { const eIndex = newNode.eIndex; eIndexes.push(eIndex); - last = eIndexes[result[result.length - 1]]; + last = eIndexes[subsequence[subsequence.length - 1]]; if (eIndex > last || last === undefined) { // 大的 eIndex直接放在最后 - preIndex[i] = result[result.length - 1]; - result.push(i); + preIndex[i] = subsequence[subsequence.length - 1]; + subsequence.push(i); } else { let start = 0; - let end = result.length - 1; + let end = subsequence.length - 1; let middle; // 二分法找到需要替换的值 while (start < end) { middle = Math.floor((start + end) / 2); - if (eIndexes[result[middle]] > eIndex) { + if (eIndexes[subsequence[middle]] > eIndex) { end = middle; } else { start = middle + 1; } } - if (eIndex < eIndexes[result[start]]) { - preIndex[i] = result[start - 1]; - result[start] = i; + if (eIndex < eIndexes[subsequence[start]]) { + preIndex[i] = subsequence[start - 1]; + subsequence[start] = i; } } i++; @@ -450,13 +466,13 @@ function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newC } // 向前回溯找到正确的结果 - let length = result.length; - let prev = result[length - 1]; + let length = subsequence.length; + let prev = subsequence[length - 1]; while (length-- > 0) { - result[length] = prev; - prev = preIndex[result[length]]; + subsequence[length] = prev; + prev = preIndex[subsequence[length]]; } - result.forEach(idx => { + subsequence.forEach(idx => { // 把需要复用的节点从 restNodes 中清理掉,因为不需要打 add 标记,直接复用 dom 节点 reuseNodes[idx] = null; }); @@ -474,7 +490,43 @@ function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newC appendNode(rightNewNode); setVNodesCIndex(rightNewNode.next, rightNewNode.cIndex + 1); } +} +// diff数组类型的节点,核心算法 +function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newChildren: Array): VNode | null { + let resultingFirstChild: VNode | null = null; + let prevNewNode: VNode | null = null; + + function appendNode(newNode: VNode) { + if (prevNewNode === null) { + resultingFirstChild = newNode; + newNode.cIndex = 0; + } else { + prevNewNode.next = newNode; + newNode.cIndex = prevNewNode.cIndex + 1; + } + markVNodePath(newNode); + prevNewNode = newNode; + } + + const { oldNode, leftIdx } = diffLeftSide(firstChild, newChildren, parentNode, appendNode); + + const diffRightSideResult = diffRightSide(oldNode, leftIdx, newChildren, parentNode); + + // 3. 新节点已经处理完成 + if (leftIdx === diffRightSideResult.rightIdx) { + appendRightSideNode(oldNode, parentNode, firstChild, newChildren, diffRightSideResult, appendNode); + return resultingFirstChild; + } + + // 4. 新节点还有一部分,但是老节点已经没有了 + if (oldNode === null) { + appendAllRestNode(oldNode, parentNode, firstChild, newChildren, leftIdx, diffRightSideResult, appendNode); + return resultingFirstChild; + } + + // 5. 新节点还有一部分,但是老节点也还有一部分 + appendNodeWithLIS(oldNode, parentNode, firstChild, newChildren, leftIdx, diffRightSideResult, appendNode); return resultingFirstChild; } @@ -536,12 +588,7 @@ function diffStringNodeHandler(parentNode: VNode, newChild: any, firstChildVNode } // 新节点是对象类型 -function diffObjectNodeHandler( - parentNode: VNode, - firstChild: VNode | null, - newChild: any, - isComparing: boolean -) { +function diffObjectNodeHandler(parentNode: VNode, firstChild: VNode | null, newChild: any, isComparing: boolean) { let canReuseNode: VNode | null = null; // 通过key比对是否有可以reuse From 1961f931fb41e311b61271349264cdfc722597b0 Mon Sep 17 00:00:00 2001 From: * <8> Date: Mon, 7 Nov 2022 11:58:57 +0800 Subject: [PATCH 4/4] Match-id-65a76025e906c80ddf9f33b914818e6f89209ddf --- libs/horizon/src/renderer/TreeBuilder.ts | 2 +- .../src/renderer/diff/nodeDiffComparator.ts | 100 ++++++++++++------ 2 files changed, 71 insertions(+), 31 deletions(-) diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts index 622475be..44db6d7b 100644 --- a/libs/horizon/src/renderer/TreeBuilder.ts +++ b/libs/horizon/src/renderer/TreeBuilder.ts @@ -179,7 +179,7 @@ function isEqualByIndex(idx: number, pathArrays: string[][]) { function getChildByIndex(vNode: VNode, idx: number) { let node = vNode.child; for (let i = 0; i < idx; i++) { - if (node) { + if (node !== null) { node = node.next; } else { return null; diff --git a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts index a66a627a..a713fc88 100644 --- a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts +++ b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts @@ -231,49 +231,63 @@ function getOldNodeFromMap(nodeMap: Map, newIdx: number, return null; } -function diffLeftSide(oldNode: VNode | null, newChildren: Array, parentNode: VNode, appendNode: AppendVNode) { +/** + * 左端新老节点对比 + * @param firstNode 第一个VNode + * @param newChildren 新的JSX children + * @param parentNode 父节点 + * @param appendNode 添加节点 + */ +function diffLeftSide(firstNode: VNode | null, newChildren: Array, parentNode: VNode, appendNode: AppendVNode) { let nextOldNode: VNode | null = null; // 从左边开始的位置 let leftIdx = 0; // 1. 从左侧开始比对currentVNode和newChildren,若不能复用则跳出循环 - for (; oldNode !== null && leftIdx < newChildren.length; leftIdx++) { - if (oldNode.eIndex > leftIdx) { + for (; firstNode !== null && leftIdx < newChildren.length; leftIdx++) { + if (firstNode.eIndex > leftIdx) { // 当新旧节点位置不一,则将缓存当前的旧节点,放到下一次对比 - nextOldNode = oldNode; - oldNode = null; + nextOldNode = firstNode; + firstNode = null; } else { - nextOldNode = oldNode.next; + nextOldNode = firstNode.next; } // 不能复用,break - if (!checkCanReuseNode(oldNode, newChildren[leftIdx], leftIdx)) { - oldNode = oldNode ?? nextOldNode; + if (!checkCanReuseNode(firstNode, newChildren[leftIdx], leftIdx)) { + firstNode = firstNode ?? nextOldNode; break; } - const newNode = getNewNode(parentNode, newChildren[leftIdx], oldNode); + const newNode = getNewNode(parentNode, newChildren[leftIdx], firstNode); // 没有生成新节点,break if (!newNode) { - oldNode = oldNode ?? nextOldNode; + firstNode = firstNode ?? nextOldNode; break; } // diff过程中,需要将现有的节点清除掉,如果是创建,则不需要处理(因为没有现存节点) - if (oldNode && newNode.isCreated) { - deleteVNode(parentNode, oldNode); + if (firstNode && newNode.isCreated) { + deleteVNode(parentNode, firstNode); } setVNodeAdditionFlag(newNode); newNode.eIndex = leftIdx; appendNode(newNode); - oldNode = nextOldNode; + firstNode = nextOldNode; } - return { oldNode, leftIdx }; + return { leftEndOldNode: firstNode, leftIdx }; } +/** + * 右端新老节点对比 + * @param leftEndOldNode 左端对比完成后第一个不同的老节点 + * @param leftIdx 左端diff完成后Index + * @param newChildren 新的JSX children + * @param parentNode 父节点 + */ function diffRightSide( - oldNode: VNode | null, + leftEndOldNode: VNode | null, leftIdx: number, newChildren: Array, parentNode: VNode @@ -284,8 +298,8 @@ function diffRightSide( let rightEndOldNode; // 老节点中最右边匹配的节点引用 abcde --> abfde 则rightEndOldNode = c; let rightNewNode: VNode | null = null; // 最右边匹配的节点引用 abcde --> abfde 则rightNewNode = d; // 从后往前,新资源的位置还没有到最末端,旧的vNode也还没遍历完,则可以考虑从后往前开始 - if (rightIdx > leftIdx && oldNode !== null) { - const rightRemainingOldChildren = transRightChildrenToArray(oldNode); + if (rightIdx > leftIdx && leftEndOldNode !== null) { + const rightRemainingOldChildren = transRightChildrenToArray(leftEndOldNode); let rightOldIndex: number | null = rightRemainingOldChildren.length - 1; // 2. 从右侧开始比对currentVNode和newChildren,若不能复用则跳出循环 @@ -330,8 +344,17 @@ function diffRightSide( return { rightIdx, rightEndOldNode, rightNewNode }; } +/** + * 添加所有右端Diff完成的节点 + * @param leftEndOldNode 左端对比完成后第一个不同的老节点 + * @param parentNode 父节点 + * @param firstChild 第一个VNode + * @param newChildren 新的JSX children + * @param diffRightSideResult 右端diff结果 + * @param appendNode 添加节点 + */ function appendRightSideNode( - oldNode: VNode | null, + leftEndOldNode: VNode | null, parentNode: VNode, firstChild: VNode | null, newChildren: Array, @@ -340,11 +363,12 @@ function appendRightSideNode( ) { const { rightEndOldNode, rightNewNode } = diffRightSideResult; + // 清除中间残留的节点 if (firstChild && parentNode.tag === DomComponent && newChildren.length === 0) { FlagUtils.markClear(parentNode); parentNode.clearChild = firstChild; } else { - deleteVNodes(parentNode, oldNode, rightEndOldNode); + deleteVNodes(parentNode, leftEndOldNode, rightEndOldNode); } if (rightNewNode) { @@ -353,10 +377,18 @@ function appendRightSideNode( } } +/** + * 添加左右端比较完成后剩余新节点和右侧节点 + * @param leftEndOldNode 左端对比完成后第一个不同的老节点 + * @param parentNode 父节点 + * @param newChildren 新的JSX children + * @param leftIdx 左端diff完成后Index + * @param diffRightSideResult 右端diff结果 + * @param appendNode 添加节点 + */ function appendAllRestNode( - oldNode: VNode | null, + leftEndOldNode: VNode | null, parentNode: VNode, - firstChild: VNode | null, newChildren: Array, leftIdx: number, diffRightSideResult: DiffRightSideResult, @@ -395,14 +427,20 @@ function appendAllRestNode( } } + /** * 双端对比完成,新老节点都有剩余时,构造LIS(最长递增子序列) * 属于LIS的新节点直接复用,否则新增 + * @param leftEndOldNode 左端对比完成后第一个不同的老节点 + * @param parentNode 父节点 + * @param newChildren 新的JSX children + * @param leftIdx 左端diff完成后Index + * @param diffRightSideResult 右端diff结果 + * @param appendNode 添加节点 */ function appendNodeWithLIS( - oldNode: VNode, + leftEndOldNode: VNode, parentNode: VNode, - firstChild: VNode | null, newChildren: Array, leftIdx: number, diffRightSideResult: DiffRightSideResult, @@ -411,7 +449,7 @@ function appendNodeWithLIS( const { rightIdx, rightNewNode, rightEndOldNode } = diffRightSideResult; // 把剩下的currentVNode转成Map - const leftChildrenMap = transLeftChildrenToMap(oldNode, rightEndOldNode); + const leftChildrenMap = transLeftChildrenToMap(leftEndOldNode, rightEndOldNode); // 通过贪心算法+二分法获取最长递增子序列 const eIndexes: Array = []; // 记录 eIndex 值 const subsequence: Array = []; // 记录最长子序列在eIndexes中的 index 值 @@ -509,24 +547,26 @@ function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newC prevNewNode = newNode; } - const { oldNode, leftIdx } = diffLeftSide(firstChild, newChildren, parentNode, appendNode); + // 1. 左端新老节点对比 + const { leftEndOldNode, leftIdx } = diffLeftSide(firstChild, newChildren, parentNode, appendNode); - const diffRightSideResult = diffRightSide(oldNode, leftIdx, newChildren, parentNode); + // 2. 右端新老节点对比 + const diffRightSideResult = diffRightSide(leftEndOldNode, leftIdx, newChildren, parentNode); // 3. 新节点已经处理完成 if (leftIdx === diffRightSideResult.rightIdx) { - appendRightSideNode(oldNode, parentNode, firstChild, newChildren, diffRightSideResult, appendNode); + appendRightSideNode(leftEndOldNode, parentNode, firstChild, newChildren, diffRightSideResult, appendNode); return resultingFirstChild; } // 4. 新节点还有一部分,但是老节点已经没有了 - if (oldNode === null) { - appendAllRestNode(oldNode, parentNode, firstChild, newChildren, leftIdx, diffRightSideResult, appendNode); + if (leftEndOldNode === null) { + appendAllRestNode(leftEndOldNode, parentNode, newChildren, leftIdx, diffRightSideResult, appendNode); return resultingFirstChild; } // 5. 新节点还有一部分,但是老节点也还有一部分 - appendNodeWithLIS(oldNode, parentNode, firstChild, newChildren, leftIdx, diffRightSideResult, appendNode); + appendNodeWithLIS(leftEndOldNode, parentNode, newChildren, leftIdx, diffRightSideResult, appendNode); return resultingFirstChild; }