From 4759e6516028dd17f06fd44e90a7a5f470f158f2 Mon Sep 17 00:00:00 2001 From: * <8> Date: Wed, 19 Jan 2022 10:25:41 +0800 Subject: [PATCH] Match-id-fa22e32139c3b08f8277784897028d35166c0e95 --- libs/horizon/src/renderer/Renderer.ts | 23 +- libs/horizon/src/renderer/TreeBuilder.ts | 392 ++++++++++----------- libs/horizon/src/renderer/UpdateHandler.ts | 50 +-- libs/horizon/src/renderer/vnode/VNode.ts | 2 +- 4 files changed, 238 insertions(+), 229 deletions(-) diff --git a/libs/horizon/src/renderer/Renderer.ts b/libs/horizon/src/renderer/Renderer.ts index 60ac72da..ebf8da2b 100644 --- a/libs/horizon/src/renderer/Renderer.ts +++ b/libs/horizon/src/renderer/Renderer.ts @@ -1,5 +1,5 @@ -import type {VNode} from './Types'; -import type {Update} from './UpdateHandler'; +import type { VNode } from './Types'; +import type { Update } from './UpdateHandler'; import { asyncUpdates, @@ -7,12 +7,12 @@ import { runDiscreteUpdates, launchUpdateFromVNode, } from './TreeBuilder'; -import {runAsyncEffects} from './submit/HookEffectHandler'; -import {Callback, newUpdate, pushUpdate} from './UpdateHandler'; -import {getFirstChild} from './vnode/VNodeUtils'; +import { runAsyncEffects } from './submit/HookEffectHandler'; +import { Callback, newUpdate, pushUpdate } from './UpdateHandler'; +import { getFirstChild } from './vnode/VNodeUtils'; -export {createVNode} from './vnode/VNodeCreator'; -export {createPortal} from './components/CreatePortal'; +export { createVNode } from './vnode/VNodeCreator'; +export { createPortal } from './components/CreatePortal'; export { asyncUpdates, syncUpdates, @@ -26,7 +26,7 @@ export function startUpdate( callback?: Callback, ) { const update: Update = newUpdate(); - update.content = {element}; + update.content = { element }; if (typeof callback === 'function') { update.callback = callback; @@ -38,10 +38,9 @@ export function startUpdate( } export function getFirstCustomDom(treeRoot: VNode): Element | Text | null { - if (!treeRoot.child) { - return null; + if (treeRoot?.child) { + return treeRoot.child.realNode; } - - return treeRoot.child.realNode; + return null; } diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts index cc0e5ba0..a628048b 100644 --- a/libs/horizon/src/renderer/TreeBuilder.ts +++ b/libs/horizon/src/renderer/TreeBuilder.ts @@ -1,16 +1,16 @@ -import type {VNode} from './Types'; +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 { 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 { findDomParent, getSiblingVNode } from './vnode/VNodeUtils'; import { ByAsync, BySync, @@ -21,7 +21,7 @@ import { isExecuting, setExecuteMode } from './ExecuteMode'; -import {recoverParentsContextCtx, resetNamespaceCtx, setNamespaceCtx} from './ContextSaver'; +import { recoverParentsContextCtx, resetNamespaceCtx, setNamespaceCtx } from './ContextSaver'; import { updateChildShouldUpdate, updateParentsChildShouldUpdate, @@ -61,132 +61,6 @@ 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 = getChildByIndex(node, pathIndex); - } - - return node; -} - -function getChildByIndex(vNode: VNode, idx: number) { - let node = vNode.child; - for (let i = 0; i < idx; i++) { - node = node.next; - } - 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 @@ -195,6 +69,71 @@ function resetProcessingVariables(startUpdateVNode: VNode) { unrecoverableErrorDuringBuild = null; } +// ============================== 向上递归 ============================== + +// 尝试完成当前工作单元,然后移动到下一个兄弟工作单元。如果没有更多的同级,请返回父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); + } +} + +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); +} + // ============================== 深度遍历 ============================== function buildVNodeTree(treeRoot: VNode) { const preMode = copyExecuteMode(); @@ -251,20 +190,130 @@ function buildVNodeTree(treeRoot: VNode) { setExecuteMode(preMode); } -function handleError(root, error): void { - if (processing === null || processing.parent === null) { - // 这是一个致命的错误,因为没有祖先可以处理它 - setBuildResult(BuildFatalErrored); - unrecoverableErrorDuringBuild = error; +// 总体任务入口 +function renderFromRoot(treeRoot) { + runAsyncEffects(); - processing = null; - return; + // 1. 构建vNode树 + buildVNodeTree(treeRoot); + + // 致命错误直接抛出 + if (getBuildResult() === BuildFatalErrored) { + throw unrecoverableErrorDuringBuild; } - // 处理capture和bubble阶段抛出的错误 - handleRenderThrowError(processing, error); + // 2. 提交变更 + submitToRender(treeRoot); - bubbleVNode(processing); + return null; +} + +// 尝试去渲染,已有任务就跳出 +export function tryRenderRoot(treeRoot: VNode) { + if (treeRoot.shouldUpdate && treeRoot.task === null) { + // 任务放进queue,但是调度开始还是异步的 + treeRoot.task = pushRenderCallback( + renderFromRoot.bind(null, treeRoot), + ); + } +} + +// 发起更新 +export function launchUpdateFromVNode(vNode: VNode): null | void { + // 检查循环调用 + 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(); + } + } +} + +// 判断数组中节点的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; +} + +function getChildByIndex(vNode: VNode, idx: number) { + let node = vNode.child; + for (let i = 0; i < idx; i++) { + node = node.next; + } + return node; +} + +// 从多个更新节点中,计算出开始节点。即:找到最近的共同的父辈节点 +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 = getChildByIndex(node, pathIndex); + } + + return node; } export function setBuildResultError() { @@ -273,55 +322,6 @@ export function setBuildResultError() { } } -// ============================== 向上递归 ============================== - -// 尝试完成当前工作单元,然后移动到下一个兄弟工作单元。如果没有更多的同级,请返回父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的所有效果附加到父树的效果列表中,子项的完成顺序会影响副作用顺序。 diff --git a/libs/horizon/src/renderer/UpdateHandler.ts b/libs/horizon/src/renderer/UpdateHandler.ts index 5e54e365..681dab4d 100644 --- a/libs/horizon/src/renderer/UpdateHandler.ts +++ b/libs/horizon/src/renderer/UpdateHandler.ts @@ -1,10 +1,10 @@ -import type {VNode} from './Types'; -import {FlagUtils, ShouldCapture} from './vnode/VNodeFlags'; +import type { VNode } from './Types'; +import { FlagUtils, ShouldCapture } from './vnode/VNodeFlags'; export type Update = { - type: 'Update' | 'Override' | 'ForceUpdate' | 'Error', - content: any, - callback: Callback | null, + type: 'Update' | 'Override' | 'ForceUpdate' | 'Error'; + content: any; + callback: Callback | null; }; export type Callback = () => any; @@ -42,6 +42,18 @@ export function pushUpdate(vNode: VNode, update: Update) { updates.push(update); } +function getCallback( + update: Update, + inst: any, + oldState: any, + props: any,): any { + const content = update.content; + const newState = typeof content === 'function' ? content.call(inst, oldState, props) : content; + return (newState === null || newState === undefined) + ? oldState + : { ...oldState, ...newState }; +} + // 根据update获取新的state function calcState( vNode: VNode, @@ -50,24 +62,22 @@ function calcState( 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) { + switch (update.type) { + case UpdateState.Override: + const content = update.content; + return typeof content === 'function' ? content.call(inst, oldState, props) : content; + case UpdateState.ForceUpdate: + vNode.isForceUpdate = true; + return oldState; + case 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 getCallback(update, inst, oldState, props); + case UpdateState.Update: + return getCallback(update, inst, oldState, props); + default: + return oldState; } - return oldState; } // 收集callback diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 997d8bca..8c16fad4 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -26,7 +26,7 @@ export class VNode { suspensePromises: any = null; // suspense组件的promise列表 changeList: any = null; // DOM的变更列表 effectList: any[] = []; // useEffect 的更新数组 - updates: any[] = null; // TreeRoot和ClassComponent使用的更新数组 + updates: any[] | null = null; // TreeRoot和ClassComponent使用的更新数组 stateCallbacks: any[] = []; // 存放存在setState的第二个参数和HorizonDOM.render的第三个参数所在的node数组 isForceUpdate: boolean = false; // 是否使用强制更新