From c1ea8c0667ff22f8daca233814455f03e72bbd7c Mon Sep 17 00:00:00 2001 From: * <8> Date: Wed, 22 Dec 2021 20:45:13 +0800 Subject: [PATCH 1/2] Match-id-71277d02dc3a2b8708d412eecb3a00818ccdfb21 --- .../src/renderer/submit/HookEffectHandler.ts | 161 ++++++++++++++ libs/horizon/src/renderer/submit/Submit.ts | 207 ++++++++++++++++++ 2 files changed, 368 insertions(+) create mode 100644 libs/horizon/src/renderer/submit/HookEffectHandler.ts create mode 100644 libs/horizon/src/renderer/submit/Submit.ts diff --git a/libs/horizon/src/renderer/submit/HookEffectHandler.ts b/libs/horizon/src/renderer/submit/HookEffectHandler.ts new file mode 100644 index 00000000..36344ff3 --- /dev/null +++ b/libs/horizon/src/renderer/submit/HookEffectHandler.ts @@ -0,0 +1,161 @@ +/** + * useEffect 和 useLayoutEffect的执行逻辑 + */ + +import type {VNode} from '../Types'; +import type { + Effect as HookEffect, + EffectList, +} from '../hooks/HookType'; +import { + callRenderQueueImmediate, +} from '../taskExecutor/RenderQueue'; +import {runAsync} from '../taskExecutor/TaskExecutor'; +import { + copyExecuteMode, InRender, setExecuteMode,changeMode +} from '../ExecuteMode'; +import {handleSubmitError} from '../ErrorHandler'; +import {clearDirtyNodes} from './Submit'; +import {EffectConstant} from '../hooks/EffectConstant'; + +let hookEffects: Array = []; +let hookRemoveEffects: Array = []; +// 是否正在异步调度effects +let isScheduling: boolean = false; +let hookEffectRoot: VNode | null = null; + +export function setSchedulingEffects(value) { + isScheduling = value; +} +export function isSchedulingEffects() { + return isScheduling; +} + +export function setHookEffectRoot(root: VNode | null) { + hookEffectRoot = root; +} +export function getHookEffectRoot() { + return hookEffectRoot; +} + +export function callUseEffects(vNode: VNode) { + const effectList: EffectList = vNode.effectList; + + effectList.forEach(effect => { + const {effectConstant} = effect; + if ( + (effectConstant & EffectConstant.Effect) !== EffectConstant.NoEffect && + (effectConstant & EffectConstant.DepsChange) !== EffectConstant.NoEffect + ) { + hookEffects.push({effect, vNode}); + hookRemoveEffects.push({effect, vNode}); + + // 异步调用 + if (!isScheduling) { + isScheduling = true; + runAsync(runAsyncEffects); + } + } + }); +} + +export function runAsyncEffects() { + if (hookEffectRoot === null) { + return false; + } + + const root = hookEffectRoot; + hookEffectRoot = null; + + const preMode = copyExecuteMode(); + changeMode(InRender, true); + + // 调用effect destroy + const removeEffects = hookRemoveEffects; + hookRemoveEffects = []; + removeEffects.forEach(({effect, vNode}) => { + const destroy = effect.removeEffect; + effect.removeEffect = undefined; + + if (typeof destroy === 'function') { + try { + destroy(); + } catch (error) { + handleSubmitError(vNode, error); + } + } + }); + + // 调用effect create + const createEffects = hookEffects; + hookEffects = []; + createEffects.forEach(({effect, vNode}) => { + try { + const create = effect.effect; + + effect.removeEffect = create(); + } catch (error) { + handleSubmitError(vNode, error); + } + }); + + // 清理dirtyNodes + clearDirtyNodes(root.dirtyNodes); + + setExecuteMode(preMode); + + callRenderQueueImmediate(); + + return true; +} + +// 在销毁vNode的时候调用remove +export function callEffectRemove(vNode: VNode) { + const effectList: EffectList = vNode.effectList; + + effectList.forEach(effect => { + const {removeEffect, effectConstant} = effect; + + if (removeEffect !== undefined) { + if ((effectConstant & EffectConstant.Effect) !== EffectConstant.NoEffect) { // 如果是useEffect,就异步调用 + hookRemoveEffects.push({effect, vNode}); + + if (!isScheduling) { + isScheduling = true; + runAsync(runAsyncEffects); + } + } else { // 是useLayoutEffect,直接执行 + removeEffect(); + } + } + }); +} + +// 同步执行UseLayoutEffect的remove +export function callUseLayoutEffectRemove(vNode: VNode) { + const effectList: EffectList = vNode.effectList; + + const layoutLabel = EffectConstant.LayoutEffect | EffectConstant.DepsChange; + effectList.forEach(item => { + if ((item.effectConstant & layoutLabel) === layoutLabel) { + const remove = item.removeEffect; + item.removeEffect = undefined; + if (typeof remove === 'function') { + remove(); + } + } + }); +} + +// 同步执行UseLayoutEffect +export function callUseLayoutEffectCreate(vNode: VNode) { + const effectList: EffectList = vNode.effectList; + + const layoutLabel = EffectConstant.LayoutEffect | EffectConstant.DepsChange; + effectList.forEach(item => { + if ((item.effectConstant & layoutLabel) === layoutLabel) { + const create = item.effect; + item.removeEffect = create(); + } + }); +} diff --git a/libs/horizon/src/renderer/submit/Submit.ts b/libs/horizon/src/renderer/submit/Submit.ts new file mode 100644 index 00000000..654d5367 --- /dev/null +++ b/libs/horizon/src/renderer/submit/Submit.ts @@ -0,0 +1,207 @@ +import type {VNode} from '../Types'; + +import {callRenderQueueImmediate} from '../taskExecutor/RenderQueue'; +import {throwIfTrue} from '../utils/throwIfTrue'; +import {FlagUtils, Addition as AdditionFlag} from '../vnode/VNodeFlags'; +import {prepareForSubmit, resetAfterSubmit} from '../../dom/DOMOperator'; +import {handleSubmitError} from '../ErrorHandler'; +import { + attachRef, + callAfterSubmitLifeCycles, + callBeforeSubmitLifeCycles, submitDeletion, submitAddition, + submitResetTextContent, submitUpdate, detachRef, +} from './LifeCycleHandler'; +import {tryRenderRoot, setProcessing, getStartVNode} from '../TreeBuilder'; +import { + BySync, + InRender, + copyExecuteMode, + setExecuteMode, + checkMode, + changeMode, +} from '../ExecuteMode'; +import { + isSchedulingEffects, + setSchedulingEffects, setHookEffectRoot, +} from './HookEffectHandler'; + +let rootThrowError = null; + +// 防止死循环调用update +const LOOPING_UPDATE_LIMIT = 50; +let loopingUpdateCount: number = 0; +let lastRoot: VNode | null = null; + +export function submitToRender(treeRoot) { + treeRoot.shouldUpdate = treeRoot.childShouldUpdate; + + const startVNode = getStartVNode(); + + // 置空task,让才能加入新的render任务 + treeRoot.task = null; + + setProcessing(null); + + if (FlagUtils.hasAnyFlag(startVNode)) { + // 把自己加上 + startVNode.dirtyNodes.push(startVNode); + } + + const dirtyNodes = startVNode.dirtyNodes; + if (dirtyNodes.length) { + const preMode = copyExecuteMode(); + changeMode(InRender, true); + + prepareForSubmit(); + // before submit阶段 + beforeSubmit(dirtyNodes); + + // submit阶段 + submit(dirtyNodes); + + resetAfterSubmit(); + + // after submit阶段 + afterSubmit(dirtyNodes); + + setExecuteMode(preMode) + } + + if (isSchedulingEffects()) { + setSchedulingEffects(false); + + // 记录root,说明这个root有副作用要执行 + setHookEffectRoot(treeRoot); + } else { + clearDirtyNodes(dirtyNodes); + } + + // 统计root同步重渲染的次数,如果太多可能是无线循环 + countLoopingUpdate(treeRoot); + + // 在退出`submit` 之前始终调用此函数,以确保任何已计划在此根上执行的update被执行。 + tryRenderRoot(treeRoot); + + if (rootThrowError) { + const error = rootThrowError; + rootThrowError = null; + throw error; + } + + // 非批量:即同步执行的,没有必要去执行RenderQueue,RenderQueue放的是异步的 + if (!checkMode(BySync)) { // 非批量 + callRenderQueueImmediate(); + } + + return null; +} + +function beforeSubmit(dirtyNodes: Array) { + dirtyNodes.forEach(node => { + try { + if (node.flags.Snapshot) { + callBeforeSubmitLifeCycles(node); + } + } catch (error) { + throwIfTrue(node === null, 'Should be working on an effect.'); + handleSubmitError(node, error); + } + }); +} + +function submit(dirtyNodes: Array) { + dirtyNodes.forEach(node => { + try { + if (node.flags.ResetText) { + submitResetTextContent(node); + } + + if (node.flags.Ref) { + if (!node.isCreated) { + // 需要执行 + detachRef(node, true); + } + } + + const {Addition, Update, Deletion} = node.flags; + if (Addition && Update) { + // Addition + submitAddition(node); + FlagUtils.removeFlag(node, AdditionFlag); + + // Update + submitUpdate(node); + } else { + if (Addition) { + submitAddition(node); + FlagUtils.removeFlag(node, AdditionFlag); + } else if (Update) { + submitUpdate(node); + } else if (Deletion) { + submitDeletion(node); + } + } + } catch (error) { + throwIfTrue(node === null, 'Should be working on an effect.'); + handleSubmitError(node, error); + } + }); +} + +function afterSubmit(dirtyNodes: Array) { + dirtyNodes.forEach(node => { + try { + if (node.flags.Update || node.flags.Callback) { + callAfterSubmitLifeCycles(node); + } + + if (node.flags.Ref) { + attachRef(node); + } + } catch (error) { + throwIfTrue(node === null, 'Should be working on an effect.'); + handleSubmitError(node, error); + } + }); +} + +export function setRootThrowError(error: any) { + if (!rootThrowError) { + rootThrowError = error; + } +} + +// 统计root同步重渲染的次数,如果太多可能是无限循环 +function countLoopingUpdate(root: VNode) { + if (root.shouldUpdate) { + if (root === lastRoot) { + loopingUpdateCount++; + } else { + loopingUpdateCount = 0; + lastRoot = root; + } + } else { + loopingUpdateCount = 0; + } +} + +export function checkLoopingUpdateLimit() { + if (loopingUpdateCount > LOOPING_UPDATE_LIMIT) { + loopingUpdateCount = 0; + lastRoot = null; + + throw Error( + `The number of updates exceeds the upper limit ${LOOPING_UPDATE_LIMIT}. + A component maybe repeatedly invokes setState on componentWillUpdate or componentDidUpdate.` + ); + } +} + +// 清理dirtyNodes +export function clearDirtyNodes(dirtyNodes) { + dirtyNodes.forEach(node => { + if (node.flags.Deletion) { + node.realNode = null; + } + }); +} From c9b67f1184963de3151dfc81f9122e182823903c Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 23 Dec 2021 10:56:10 +0800 Subject: [PATCH 2/2] Match-id-fbd10155c13bc66747c4d9275c88faa15bf2bcf4 --- .../src/renderer/submit/LifeCycleHandler.ts | 359 ++++++++++++++++++ 1 file changed, 359 insertions(+) create mode 100644 libs/horizon/src/renderer/submit/LifeCycleHandler.ts diff --git a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts new file mode 100644 index 00000000..97322327 --- /dev/null +++ b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts @@ -0,0 +1,359 @@ +/** + * 该文件负责把更新应用到界面上 以及 和生命周期的相关调用 + */ + +import type {Container} from '../../dom/DOMOperator'; +import type {RefType, VNode} from '../Types'; + +import {listenToPromise, SuspenseChildStatus} from '../render/SuspenseComponent'; +import { + FunctionComponent, + ForwardRef, + ClassComponent, + TreeRoot, + DomComponent, + DomText, + DomPortal, + SuspenseComponent, + MemoComponent, +} from '../vnode/VNodeTags'; +import {FlagUtils, ResetText} from '../vnode/VNodeFlags'; +import {mergeDefaultProps} from '../render/LazyComponent'; +import { + submitDomUpdate, + clearText, + appendChildElement, + insertDomBefore, + removeChildDom, + hideDom, + unHideDom, + clearContainer, +} from '../../dom/DOMOperator'; +import {callEffectRemove, callUseEffects, callUseLayoutEffectCreate, callUseLayoutEffectRemove} from './HookEffectHandler'; +import {handleSubmitError} from '../ErrorHandler'; +import { + travelVNodeTree, + clearVNode, + isDomVNode, + findDomParent, getSiblingDom, +} from '../vnode/VNodeUtils'; +import {shouldAutoFocus} from '../../dom/utils/Common'; + +function callComponentWillUnmount(vNode: VNode, instance: any) { + try { + instance.componentWillUnmount(); + } catch (error) { + handleSubmitError(vNode, error); + } +} + +// 调用界面变化前的生命周期 +function callBeforeSubmitLifeCycles( + vNode: VNode, +): void { + switch (vNode.tag) { + case ClassComponent: { // 调用instance.getSnapshotBeforeUpdate + if (!vNode.isCreated) { + const prevProps = vNode.isLazyComponent + ? mergeDefaultProps(vNode.type, vNode.oldProps) + : vNode.oldProps; + const prevState = vNode.oldState; + const instance = vNode.realNode; + + const snapshot = instance.getSnapshotBeforeUpdate(prevProps, prevState); + + // __snapshotResult会在调用componentDidUpdate的时候作为第三个参数 + instance.__snapshotResult = snapshot; + } + return; + } + case TreeRoot: { + const root = vNode.realNode; + clearContainer(root.outerDom); + return; + } + } +} + +// 调用vNode.stateCallbacks +function callStateCallback(vNode: VNode, obj: any): void { + const stateCallbacks = vNode.stateCallbacks; + vNode.stateCallbacks = []; + + stateCallbacks.forEach(callback => { + if (typeof callback === 'function') { + callback.call(obj); + } + }); +} + +// 调用界面变化后的生命周期 +function callAfterSubmitLifeCycles( + vNode: VNode, +): void { + switch (vNode.tag) { + case FunctionComponent: + case ForwardRef: { + // 执行useLayoutEffect的create方法 + callUseLayoutEffectCreate(vNode); + + callUseEffects(vNode); + return; + } + case ClassComponent: { + const instance = vNode.realNode; + if (vNode.flags.Update) { + if (vNode.isCreated) { + instance.componentDidMount(); + } else { + const prevProps = + vNode.isLazyComponent + ? mergeDefaultProps(vNode.type, vNode.oldProps) + : vNode.oldProps; + const prevState = vNode.oldState; + + instance.componentDidUpdate( + prevProps, + prevState, + instance.__snapshotResult, + ); + } + } + + callStateCallback(vNode, instance); + return; + } + case TreeRoot: { + const childVNode = (vNode.children && vNode.children.length) ? vNode.children[0] : null; + const instance = childVNode !== null ? childVNode.realNode : null; + callStateCallback(vNode, instance); + return; + } + case DomComponent: { + if (vNode.isCreated && vNode.flags.Update) { + // button、input、select、textarea、如果有 autoFocus 属性需要focus + if (shouldAutoFocus(vNode.type, vNode.props)) { + // button、input、select、textarea、如果有 autoFocus 属性需要focus + vNode.realNode.focus(); + } + } + return; + } + } +} + +function hideOrUnhideAllChildren(vNode, isHidden) { + travelVNodeTree(vNode, (node: VNode) => { + const instance = node.realNode; + + if (node.tag === DomComponent || node.tag === DomText) { + if (isHidden) { + hideDom(node.tag, instance); + } else { + unHideDom(node.tag, instance, node.props); + } + } + }); +} + +function attachRef(vNode: VNode) { + const ref = vNode.ref; + if (ref !== null) { + const instance = vNode.realNode; + + if (typeof ref === 'function') { + ref(instance); + } else { + (ref).current = instance; + } + } +} + +function detachRef(vNode: VNode, isOldRef?: boolean) { + let ref = (isOldRef ? vNode.oldRef : vNode.ref); + + if (ref !== null) { + if (typeof ref === 'function') { + try { + ref(null); + } catch (error) { + handleSubmitError(vNode, error); + } + } else { + (ref).current = null; + } + } +} + +// 卸载一个vNode,不会递归 +function unmountVNode(vNode: VNode): void { + // TODO 暂时用于规避error处理逻辑,后续删除 + if (vNode.flags.Addition) { + return; + } + + switch (vNode.tag) { + case FunctionComponent: + case ForwardRef: + case MemoComponent: { + callEffectRemove(vNode); + break; + } + case ClassComponent: { + detachRef(vNode); + + const instance = vNode.realNode; + // 当constructor中抛出异常时,instance会是null,这里判断一下instance是否为空 + if (instance && typeof instance.componentWillUnmount === 'function') { + callComponentWillUnmount(vNode, instance); + } + break; + } + case DomComponent: { + detachRef(vNode); + break; + } + case DomPortal: { + // 这里会递归 + unmountDomComponents(vNode); + break; + } + } +} + +// 卸载vNode,递归遍历子vNode +function unmountNestedVNodes(vNode: VNode): void { + travelVNodeTree(vNode, (node) => { + unmountVNode(node); + }, (node) => { + // 如果是DomPortal,不需要遍历child + return node.tag === DomPortal; + }); +} + +function submitAddition(vNode: VNode): void { + const {parent, parentDom} = findDomParent(vNode); + + if (parent.flags.ResetText) { + // 在insert之前先reset + clearText(parentDom); + FlagUtils.removeFlag(parent, ResetText); + } + + const before = getSiblingDom(vNode); + insertOrAppendPlacementNode(vNode, before, parentDom); +} + +function insertOrAppendPlacementNode( + node: VNode, + beforeDom: Element | null, + parent: Element | Container, +): void { + const {tag, realNode} = node; + + if (isDomVNode(node)) { + if (beforeDom) { + insertDomBefore(parent, realNode, beforeDom); + } else { + appendChildElement(parent, realNode); + } + } else if (tag === DomPortal) { + // 这里不做处理,直接在portal中处理 + } else { + // 插入子节点们 + const children = node.children || []; + children.forEach(child => { + insertOrAppendPlacementNode(child, beforeDom, parent); + }); + } +} + +// 遍历所有子节点:删除dom节点,detach ref 和 调用componentWillUnmount() +function unmountDomComponents(vNode: VNode): void { + let currentParentIsValid = false; + + // 这两个变量要一起更新 + let currentParent; + + travelVNodeTree(vNode, (node) => { + if (!currentParentIsValid) { + const parentObj = findDomParent(node); + currentParent = parentObj.parentDom; + currentParentIsValid = true; + } + + if (node.tag === DomComponent || node.tag === DomText) { + // 卸载vNode,递归遍历子vNode + unmountNestedVNodes(node); + + // 在所有子项都卸载后,删除dom树中的节点 + removeChildDom(currentParent, node.realNode); + } else if (node.tag === DomPortal) { + if (node.children.length) { + currentParent = node.outerDom; + } + } else { + unmountVNode(node); + } + }, (node) => { + // 如果是dom不用再遍历child + return node.tag === DomComponent || node.tag === DomText; + }, null, (node) => { + if (node.tag === DomPortal) { + // 当离开portal,需要重新设置parent + currentParentIsValid = false; + } + }); +} + +function submitDeletion(vNode: VNode): void { + // 遍历所有子节点:删除dom节点,detach ref 和 调用componentWillUnmount() + unmountDomComponents(vNode); + + // 置空vNode + clearVNode(vNode); +} + +function submitUpdate(vNode: VNode): void { + switch (vNode.tag) { + case FunctionComponent: + case ForwardRef: + case MemoComponent: { + // 执行useLayoutEffect的remove方法 + callUseLayoutEffectRemove(vNode); + break; + } + case DomComponent: + case DomText: { + submitDomUpdate(vNode.tag, vNode); + break; + } + case SuspenseComponent: { + submitSuspenseComponent(vNode); + listenToPromise(vNode); + break; + } + } +} + +function submitSuspenseComponent(finishedWork: VNode) { + const suspenseChildStatus = finishedWork.suspenseChildStatus; + if (suspenseChildStatus !== SuspenseChildStatus.Init) { + hideOrUnhideAllChildren(finishedWork.children[0], suspenseChildStatus === SuspenseChildStatus.ShowFallback); + } +} + +function submitResetTextContent(vNode: VNode) { + clearText(vNode.realNode); +} + +export { + callBeforeSubmitLifeCycles, + submitResetTextContent, + submitAddition, + submitDeletion, + submitUpdate, + callAfterSubmitLifeCycles, + attachRef, + detachRef, +};