diff --git a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts index cf2a7993..b1f1203b 100644 --- a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts +++ b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts @@ -1,7 +1,7 @@ import type { VNode } from '../Types'; import { FlagUtils } from '../vnode/VNodeFlags'; import { TYPE_COMMON_ELEMENT, TYPE_FRAGMENT, TYPE_PORTAL } from '../../external/JSXElementType'; -import { DomText, DomPortal, Fragment } from '../vnode/VNodeTags'; +import { DomText, DomPortal, Fragment, DomComponent } from '../vnode/VNodeTags'; import {updateVNode, createVNode, createVNodeFromElement, updateVNodePath} from '../vnode/VNodeCreator'; import { isSameType, @@ -320,7 +320,12 @@ function diffArrayNodes( // 3. 新节点已经处理完成 if (leftIdx === rightIdx) { if (isComparing) { - deleteVNodes(parentNode, oldNode, rightEndOldNode); + if (firstChild && parentNode.tag === DomComponent && newChildren.length === 0) { + FlagUtils.markClear(parentNode); + parentNode.clearChild = firstChild; + } else { + deleteVNodes(parentNode, oldNode, rightEndOldNode); + } } if (rightNewNode) { diff --git a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts index 78a308d4..68503458 100644 --- a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts +++ b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts @@ -17,7 +17,7 @@ import { SuspenseComponent, MemoComponent, } from '../vnode/VNodeTags'; -import { FlagUtils, ResetText } from '../vnode/VNodeFlags'; +import { FlagUtils, ResetText, Clear } from '../vnode/VNodeFlags'; import { mergeDefaultProps } from '../render/LazyComponent'; import { submitDomUpdate, @@ -307,6 +307,42 @@ function unmountDomComponents(vNode: VNode): void { }); } +function submitClear(vNode: VNode): void { + const realNode = vNode.realNode; + const cloneDom = realNode.cloneNode(false); // 复制节点后horizon添加给dom的属性未能复制 + // 真实 dom 获取的keys只包含新增的属性 + // 比如真实 dom 拿到的 keys 一般只有两个 horizon 自定义属性 + // 但考虑到用户可能自定义其他属性,所以采用遍历赋值的方式 + const customizeKeys = Object.keys(realNode); + const keyLength = customizeKeys.length; + for(let i = 0; i < keyLength; i++) { + const key = customizeKeys[i]; + // 测试代码 mock 实例的全部可遍历属性都会被Object.keys方法读取到 + // children 属性被复制意味着复制了子节点,因此要排除 + if (key !== 'children') { + cloneDom[key] = realNode[key]; // 复制cloneNode未能复制的属性 + } + } + + const parentObj = findDomParent(vNode); + const currentParent = parentObj.parentDom; + let clearChild = vNode.clearChild as VNode; // 上次渲染的child保存在clearChild属性中 + // 卸载 clearChild 和 它的兄弟节点 + while(clearChild) { + // 卸载子vNode,递归遍历子vNode + unmountNestedVNodes(clearChild); + clearVNode(clearChild); + clearChild = clearChild.next as VNode; + } + + // 在所有子项都卸载后,删除dom树中的节点 + removeChildDom(currentParent, vNode.realNode); + currentParent.append(cloneDom); + vNode.realNode = cloneDom; + FlagUtils.removeFlag(vNode, Clear); + vNode.clearChild = null; +} + function submitDeletion(vNode: VNode): void { // 遍历所有子节点:删除dom节点,detach ref 和 调用componentWillUnmount() unmountDomComponents(vNode); @@ -353,6 +389,7 @@ export { submitResetTextContent, submitAddition, submitDeletion, + submitClear, submitUpdate, callAfterSubmitLifeCycles, attachRef, diff --git a/libs/horizon/src/renderer/submit/Submit.ts b/libs/horizon/src/renderer/submit/Submit.ts index e76f0b97..446569bb 100644 --- a/libs/horizon/src/renderer/submit/Submit.ts +++ b/libs/horizon/src/renderer/submit/Submit.ts @@ -9,7 +9,7 @@ import { attachRef, callAfterSubmitLifeCycles, callBeforeSubmitLifeCycles, submitDeletion, submitAddition, - submitResetTextContent, submitUpdate, detachRef, + submitResetTextContent, submitUpdate, detachRef, submitClear, } from './LifeCycleHandler'; import {tryRenderRoot, setProcessing} from '../TreeBuilder'; import { @@ -121,7 +121,7 @@ function submit(dirtyNodes: Array) { } } - const {Addition, Update, Deletion} = node.flags; + const {Addition, Update, Deletion, Clear} = node.flags; if (Addition && Update) { // Addition submitAddition(node); @@ -138,6 +138,9 @@ function submit(dirtyNodes: Array) { } else if (Deletion) { submitDeletion(node); } + if (Clear) { + submitClear(node); + } } } catch (error) { throwIfTrue(node === null, 'Should be working on an effect.'); diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 71c0856e..e5a848de 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -60,8 +60,9 @@ export class VNode { Interrupted?: boolean; ShouldCapture?: boolean; ForceUpdate?: boolean; + Clear?: boolean; } = {}; - + clearChild: VNode | null = null; // one tree相关属性 isCreated: boolean = true; oldHooks: Array> = []; // 保存上一次执行的hook diff --git a/libs/horizon/src/renderer/vnode/VNodeFlags.ts b/libs/horizon/src/renderer/vnode/VNodeFlags.ts index 62e02be1..f511ac85 100644 --- a/libs/horizon/src/renderer/vnode/VNodeFlags.ts +++ b/libs/horizon/src/renderer/vnode/VNodeFlags.ts @@ -18,9 +18,10 @@ export const Interrupted = 'Interrupted'; export const ShouldCapture = 'ShouldCapture'; // For suspense export const ForceUpdate = 'ForceUpdate'; +export const Clear = 'Clear'; -const FlagArr = [Addition, Update, Deletion, ResetText, Callback, - DidCapture, Ref, Snapshot, Interrupted, ShouldCapture, ForceUpdate]; +const FlagArr = [Addition, Update, Deletion, Clear, ResetText, Callback, + DidCapture, Ref, Snapshot, Interrupted, ShouldCapture, ForceUpdate]; const LifecycleEffectArr = [Update, Callback, Ref, Snapshot]; @@ -90,5 +91,9 @@ export class FlagUtils { static markForceUpdate(node: VNode) { node.flags.ForceUpdate = true; } + + static markClear(node: VNode) { + node.flags.Clear = true; + } }