diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts new file mode 100644 index 00000000..daed59c5 --- /dev/null +++ b/libs/horizon/src/renderer/TreeBuilder.ts @@ -0,0 +1,367 @@ +import type {VNode} from './Types'; + +import {callRenderQueueImmediate, pushRenderCallback} from './taskExecutor/RenderQueue'; +import {updateVNode} from './vnode/VNodeCreator'; +import {TreeRoot} from './vnode/VNodeTags'; +import {FlagUtils} from './vnode/VNodeFlags'; +import {captureVNode} from './render/BaseComponent'; +import {checkLoopingUpdateLimit, submitToRender} from './submit/Submit'; +import {runAsyncEffects} from './submit/HookEffectHandler'; +import {handleRenderThrowError} from './ErrorHandler'; +import componentRenders from './render'; +import ProcessingVNode from './vnode/ProcessingVNode'; +import {findDomParent, getSiblingVNode} from './vnode/VNodeUtils'; +import { + ByAsync, + BySync, + changeMode, + checkMode, + copyExecuteMode, + InRender, + isExecuting, + setExecuteMode +} from './ExecuteMode'; +import {recoverParentsContextCtx, resetNamespaceCtx, setNamespaceCtx} from './ContextSaver'; +import { + updateChildShouldUpdate, + updateParentsChildShouldUpdate, + updateShouldUpdateOfTree +} from './vnode/VNodeShouldUpdate'; + +type BuildVNodeResult = 0 | 1 | 2 | 3; +const BuildInComplete = 0; +const BuildFatalErrored = 1; +const BuildErrored = 2; +const BuildCompleted = 3; + +// 当前运行的vNode节点 +let processing: VNode | null = null; +// 根节点退出build tree时的状态,如: completed, incomplete, errored, fatalErrored. +let buildVNodeResult: BuildVNodeResult = BuildInComplete; +// 不可恢复错误 +let unrecoverableErrorDuringBuild: any = null; + +function setBuildResult(result: BuildVNodeResult) { + buildVNodeResult = result; +} + +function getBuildResult(): BuildVNodeResult { + return buildVNodeResult; +} + +export function setProcessing(vNode: VNode | null) { + processing = vNode; +} + +let startVNode: VNode | null = null; +export function getStartVNode(): VNode | null { + return startVNode; +} +export function setStartVNode(vNode: VNode | null) { + startVNode = vNode; +} + +// 发起更新 +export function launchUpdateFromVNode(vNode: VNode) { + // 检查循环调用 + checkLoopingUpdateLimit(); + + // 从当前vNode向上遍历到根节点,修改vNode.shouldUpdate和parent.childShouldUpdate + const treeRoot = updateShouldUpdateOfTree(vNode); + if (treeRoot === null) { + // 可能场景是:the componentWillUnmount method 或 useEffect cleanup function 方法中写异步任务,并且修改state。 + // 因为异步回调的时候root都可能被清除了。 + return null; + } + + // 保存待刷新的节点 + treeRoot.toUpdateNodes.add(vNode); + + if (checkMode(BySync) && // 非批量 + !checkMode(InRender)) { // 不是渲染阶段触发 + + // 业务直接调用Horizon.render的时候会进入这个分支,同步渲染。 + // 不能改成下面的异步,否则会有时序问题,因为业务可能会依赖这个渲染的完成。 + renderFromRoot(treeRoot); + } else { + tryRenderRoot(treeRoot); + + if (!isExecuting()) { + // 同步执行 + callRenderQueueImmediate(); + } + } +} + +// 从多个更新节点中,计算出开始节点。即:找到最近的共同的父辈节点 +export function calcStartUpdateVNode(treeRoot: VNode) { + const toUpdateNodes = Array.from(treeRoot.toUpdateNodes); + + if (toUpdateNodes.length === 0) { + return treeRoot; + } + + if (toUpdateNodes.length === 1) { + return toUpdateNodes[0]; + } + + // 要计算的节点过多,直接返回根节点 + if (toUpdateNodes.length > 100) { + return treeRoot; + } + + // 找到路径最短的长度 + let minPath = toUpdateNodes[0].path.length; + for (let i = 1; i < toUpdateNodes.length; i++) { + let pathLen = toUpdateNodes[i].path.length; + if (pathLen < minPath) { + minPath = pathLen; + } + } + + // 找出开始不相等的idx + let idx = 0; + for (; idx < minPath; idx++) { + if (!isEqualByIndex(idx, toUpdateNodes)) { + break; + } + } + // 得到相等的路径 + let startNodePath = toUpdateNodes[0].path.slice(0, idx); + + let node = treeRoot; + for (let i = 1; i < startNodePath.length; i++) { + let pathIndex = startNodePath[i]; + node = node.children[pathIndex]; + } + + return node; +} + +// 判断数组中节点的path的idx元素是否都相等 +function isEqualByIndex(idx: number, nodes: Array) { + let val = nodes[0].path[idx]; + for (let i = 1; i < nodes.length; i++) { + let node = nodes[i]; + if (val !== node.path[idx]) { + return false; + } + } + + return true; +} + +// 尝试去渲染,已有任务就跳出 +export function tryRenderRoot(treeRoot: VNode) { + if (treeRoot.shouldUpdate && treeRoot.task === null) { + // 任务放进queue,但是调度开始还是异步的 + treeRoot.task = pushRenderCallback( + renderFromRoot.bind(null, treeRoot), + ); + } +} + +// 总体任务入口 +function renderFromRoot(treeRoot) { + runAsyncEffects(); + + // 1. 构建vNode树 + buildVNodeTree(treeRoot); + + // 致命错误直接抛出 + if (getBuildResult() === BuildFatalErrored) { + throw unrecoverableErrorDuringBuild; + } + + // 2. 提交变更 + submitToRender(treeRoot); + + return null; +} + +// 为重新进行深度遍历做准备 +function resetProcessingVariables(startUpdateVNode: VNode) { + // 创建processing + processing = updateVNode(startUpdateVNode, startUpdateVNode?.props); + setBuildResult(BuildInComplete); + unrecoverableErrorDuringBuild = null; +} + +// ============================== 深度遍历 ============================== +function buildVNodeTree(treeRoot: VNode) { + const preMode = copyExecuteMode(); + changeMode(InRender, true); + + // 计算出开始节点 + const startUpdateVNode = calcStartUpdateVNode(treeRoot); + + // 缓存起来 + setStartVNode(startUpdateVNode); + + // 清空toUpdateNodes + treeRoot.toUpdateNodes.clear(); + + if (startUpdateVNode.tag !== TreeRoot) { // 不是根节点 + // 设置namespace,用于createElement + const parentObj = findDomParent(startUpdateVNode); + + // 当在componentWillUnmount中调用setState,parent可能是null,因为startUpdateVNode会被clear + if (parentObj !== null) { + const domParent = parentObj.parent; + resetNamespaceCtx(domParent); + setNamespaceCtx(domParent, domParent.outerDom); + } + + // 恢复父节点的context + recoverParentsContextCtx(startUpdateVNode); + } + + // 重置环境变量 + resetProcessingVariables(startUpdateVNode); + + do { + try { + while (processing !== null) { + // 捕获创建 vNodes + const next = captureVNode(processing); + + // @ts-ignore + if (!next || !next.length) { + // 如果没有产生新的,那么就完成当前节点,向上遍历 + bubbleVNode(processing); + } else { + processing = next[0]; + } + + ProcessingVNode.val = null; + } + break; + } catch (thrownValue) { + handleError(treeRoot, thrownValue); + } + } while (true); + + setExecuteMode(preMode); +} + +function handleError(root, error): void { + if (processing === null || processing.parent === null) { + // 这是一个致命的错误,因为没有祖先可以处理它 + setBuildResult(BuildFatalErrored); + unrecoverableErrorDuringBuild = error; + + processing = null; + return; + } + + // 处理capture和bubble阶段抛出的错误 + handleRenderThrowError(processing, error); + + bubbleVNode(processing); +} + +export function setBuildResultError() { + if (getBuildResult() !== BuildCompleted) { + setBuildResult(BuildErrored); + } +} + +// ============================== 向上递归 ============================== + +// 尝试完成当前工作单元,然后移动到下一个兄弟工作单元。如果没有更多的同级,请返回父vNode。 +function bubbleVNode(vNode: VNode): void { + let node = vNode; + + do { + const parent = node.parent; + + if (!node.flags.Interrupted) { // vNode没有抛出异常 + componentRenders[node.tag].bubbleRender(node); + + // 设置node的childShouldUpdate属性 + updateChildShouldUpdate(node); + + if (parent !== null && node !== getStartVNode() && !parent.flags.Interrupted) { + collectDirtyNodes(node, parent); + } + } + + // 回到了开始遍历的节点 + if (node === getStartVNode()) { + if (node.tag !== TreeRoot) { + // 设置父node的childShouldUpdate属性 + updateParentsChildShouldUpdate(node); + } + + processing = null; + break; + } + + const siblingVNode = getSiblingVNode(node); + if (siblingVNode !== null) { // 有兄弟vNode + processing = siblingVNode; + return; + } + + // 继续遍历parent + node = parent; + // 更新processing,抛出异常时可以使用 + processing = node; + } while (node !== null); + + // 修改结果 + if (getBuildResult() === BuildInComplete) { + setBuildResult(BuildCompleted); + } +} + +// 收集有变化的节点,在submit阶段继续处理 +function collectDirtyNodes(vNode: VNode, parent: VNode): void { + // 将子树和此vNode的所有效果附加到父树的效果列表中,子项的完成顺序会影响副作用顺序。 + parent.dirtyNodes.push(...vNode.dirtyNodes); + + if (FlagUtils.hasAnyFlag(vNode)) { + parent.dirtyNodes.push(vNode); + } +} + +// ============================== HorizonDOM使用 ============================== +export function runDiscreteUpdates() { + if (checkMode(ByAsync) || checkMode(InRender)) { + // 已经渲染,不能再同步执行待工作的任务,有可能是被生命周期或effect触发的事件导致的,如el.focus() + return; + } + + runAsyncEffects(); +} + +export function asyncUpdates(fn, ...param) { + const preMode = copyExecuteMode(); + changeMode(ByAsync, true); + try { + return fn(...param); + } finally { + setExecuteMode(preMode); + if (!isExecuting()) { + // 同步执行 + callRenderQueueImmediate(); + } + } +} + +export function syncUpdates(fn) { + const preMode = copyExecuteMode(); + // 去掉异步状态,添加同步状态 + changeMode(ByAsync, false); + changeMode(BySync, true); + + try { + return fn(); + } finally { + setExecuteMode(preMode); + if (!isExecuting()) { + // 同步执行 + callRenderQueueImmediate(); + } + } +} diff --git a/libs/horizon/src/renderer/UpdateHandler.ts b/libs/horizon/src/renderer/UpdateHandler.ts new file mode 100644 index 00000000..5e54e365 --- /dev/null +++ b/libs/horizon/src/renderer/UpdateHandler.ts @@ -0,0 +1,111 @@ +import type {VNode} from './Types'; +import {FlagUtils, ShouldCapture} from './vnode/VNodeFlags'; + +export type Update = { + type: 'Update' | 'Override' | 'ForceUpdate' | 'Error', + content: any, + callback: Callback | null, +}; + +export type Callback = () => any; + +export type Updates = Array | null; + +export enum UpdateState { + Update = 'Update', + Override = 'Override', + ForceUpdate = 'ForceUpdate', + Error = 'Error', +} + +// 初始化更新数组 +export function createUpdateArray(vNode: VNode): void { + vNode.updates = []; // 新产生的update会加入这个数组 +} + +// 创建update对象 +export function newUpdate(): Update { + return { + type: UpdateState.Update, // 更新的类型 + content: null, // ClassComponent的content是setState第一个参数,TreeRoot的content是HorizonDOM.render的第一个参数 + callback: null, // setState的第二个参数,HorizonDOM.render的第三个参数 + }; +} + +// 将update对象加入updates +export function pushUpdate(vNode: VNode, update: Update) { + const updates = vNode.updates; + if (updates === null) { + return; + } + + updates.push(update); +} + +// 根据update获取新的state +function calcState( + vNode: VNode, + update: Update, + inst: any, + oldState: any, + props: any, +): any { + if (update.type === UpdateState.Override) { + const content = update.content; + return typeof content === 'function' ? content.call(inst, oldState, props) : content; + } else if (update.type === UpdateState.ForceUpdate) { + vNode.isForceUpdate = true; + return oldState; + } else if (update.type === UpdateState.Error || update.type === UpdateState.Update) { + if (update.type === UpdateState.Error) { + FlagUtils.removeFlag(vNode, ShouldCapture); + FlagUtils.markDidCapture(vNode); + } + const content = update.content; + const newState = typeof content === 'function' ? content.call(inst, oldState, props) : content; + return (newState === null || newState === undefined) + ? oldState + : {...oldState, ...newState} + } + return oldState; +} + +// 收集callback +function collectCallbacks(vNode: VNode, update: Update) { + if (update.callback !== null) { + FlagUtils.markCallback(vNode); + vNode.stateCallbacks.push(update.callback); + } +} + +// 遍历处理updates, 更新vNode的state +function calcUpdates(vNode: VNode, props: any, inst: any, toProcessUpdates: Updates) { + let newState = vNode.state; + + toProcessUpdates.forEach(update => { + newState = calcState(vNode, update, inst, newState, props); + collectCallbacks(vNode, update); + }); + + vNode.shouldUpdate = false; + vNode.state = newState; +} + +// 将待更新的队列,添加到updates的尾部 +export function processUpdates(vNode: VNode, inst: any, props: any): void { + const updates: Updates = vNode.updates; + vNode.isForceUpdate = false; + + const toProcessUpdates = [...updates]; + updates.length = 0; + + if (toProcessUpdates.length) { + calcUpdates(vNode, props, inst, toProcessUpdates); + } +} + +export function pushForceUpdate(vNode: VNode) { + const update = newUpdate(); + update.type = UpdateState.ForceUpdate; + pushUpdate(vNode, update); +}