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; + } + }); +}