From 4759e6516028dd17f06fd44e90a7a5f470f158f2 Mon Sep 17 00:00:00 2001 From: * <8> Date: Wed, 19 Jan 2022 10:25:41 +0800 Subject: [PATCH 01/11] 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; // 是否使用强制更新 From c8de85302199da340a60b1003a479a0ea6da1d87 Mon Sep 17 00:00:00 2001 From: * <8> Date: Wed, 19 Jan 2022 18:09:09 +0800 Subject: [PATCH 02/11] Match-id-4496de588b05125fe488277a86323c173cecf054 --- libs/horizon/src/renderer/UpdateHandler.ts | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/libs/horizon/src/renderer/UpdateHandler.ts b/libs/horizon/src/renderer/UpdateHandler.ts index 681dab4d..473d8fe4 100644 --- a/libs/horizon/src/renderer/UpdateHandler.ts +++ b/libs/horizon/src/renderer/UpdateHandler.ts @@ -42,18 +42,6 @@ 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, @@ -72,9 +60,12 @@ function calcState( case UpdateState.Error: FlagUtils.removeFlag(vNode, ShouldCapture); FlagUtils.markDidCapture(vNode); - return getCallback(update, inst, oldState, props); case UpdateState.Update: - return getCallback(update, inst, oldState, props); + const updateContent = update.content; + const newState = typeof updateContent === 'function' ? updateContent.call(inst, oldState, props) : updateContent; + return (newState === null || newState === undefined) + ? oldState + : { ...oldState, ...newState }; default: return oldState; } From 2d797bea92ff1e92f594ef45240a16a01f1c4486 Mon Sep 17 00:00:00 2001 From: * <8> Date: Wed, 19 Jan 2022 20:15:02 +0800 Subject: [PATCH 03/11] Match-id-3d4f22ee5c412590b9561eba180766f92a003435 --- libs/horizon/src/renderer/Types.ts | 1 - libs/horizon/src/renderer/UpdateHandler.ts | 5 +---- .../src/renderer/diff/nodeDiffComparator.ts | 20 ++++++++++--------- .../src/renderer/submit/LifeCycleHandler.ts | 5 ----- libs/horizon/src/renderer/vnode/VNodeFlags.ts | 5 +++-- 5 files changed, 15 insertions(+), 21 deletions(-) diff --git a/libs/horizon/src/renderer/Types.ts b/libs/horizon/src/renderer/Types.ts index b1732fd1..2f3fad4b 100644 --- a/libs/horizon/src/renderer/Types.ts +++ b/libs/horizon/src/renderer/Types.ts @@ -2,7 +2,6 @@ export { VNode } from './vnode/VNode'; type Trigger = (A) => void; -export type ReadContextHookType = { readContext(context: ContextType): T }; export type UseStateHookType = { useState( initialState: (() => S) | S diff --git a/libs/horizon/src/renderer/UpdateHandler.ts b/libs/horizon/src/renderer/UpdateHandler.ts index 473d8fe4..d00f3ce9 100644 --- a/libs/horizon/src/renderer/UpdateHandler.ts +++ b/libs/horizon/src/renderer/UpdateHandler.ts @@ -35,11 +35,8 @@ export function newUpdate(): Update { // 将update对象加入updates export function pushUpdate(vNode: VNode, update: Update) { const updates = vNode.updates; - if (updates === null) { - return; - } - updates.push(update); + updates?.push(update); } // 根据update获取新的state diff --git a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts index 1adf32c8..606a97d9 100644 --- a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts +++ b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts @@ -354,9 +354,9 @@ function diffArrayNodes( // 把剩下的currentVNode转成Map const leftChildrenMap = transLeftChildrenToMap(oldNode, rightEndOldNode); // 通过贪心算法+二分法获取最长递增子序列 - const eIndexes = []; // 记录 eIndex 值 - const result = []; // 记录最长子序列在eIndexes中的 index 值 - const preIndex = []; // 贪心算法在替换的过程中会使得数组不正确,通过记录preIndex找到正确值 + const eIndexes: Array = []; // 记录 eIndex 值 + const result: Array = []; // 记录最长子序列在eIndexes中的 index 值 + const preIndex: Array = []; // 贪心算法在替换的过程中会使得数组不正确,通过记录preIndex找到正确值 const reuseNodes = []; // 记录复用的 VNode let i = 0; for (; leftIdx < rightIdx; leftIdx++) { @@ -367,6 +367,7 @@ function diffArrayNodes( // 从Map删除,后面不会deleteVNode leftChildrenMap.delete(newNode.key || leftIdx); } + if (oldNodeFromMap !== null) { let eIndex = newNode.eIndex; eIndexes.push(eIndex); @@ -380,7 +381,7 @@ function diffArrayNodes( let middle; // 二分法找到需要替换的值 while (start < end) { - middle = Math.floor((start + end) / 2) + middle = Math.floor((start + end) / 2); if (eIndexes[result[middle]] > eIndex) { end = middle; } else { @@ -403,6 +404,7 @@ function diffArrayNodes( appendNode(newNode); } } + if (isComparing) { // 向前回溯找到正确的结果 let length = result.length; @@ -411,9 +413,9 @@ function diffArrayNodes( result[length] = prev; prev = preIndex[result[length]]; } - result.forEach(i => { + result.forEach(idx => { // 把需要复用的节点从 restNodes 中清理掉,因为不需要打 add 标记,直接复用 dom 节点 - reuseNodes[i] = null; + reuseNodes[idx] = null; }); reuseNodes.forEach(node => { if (node !== null) { @@ -490,7 +492,7 @@ function diffStringNodeHandler( firstChildVNode: VNode, isComparing: boolean ) { - let newTextNode = null; + let newTextNode: VNode | null = null; // 第一个vNode是Text,则复用 if (firstChildVNode !== null && firstChildVNode.tag === DomText) { @@ -520,7 +522,7 @@ function diffObjectNodeHandler( firstChildVNode: VNode, isComparing: boolean ) { - let canReuseNode = null; + let canReuseNode: VNode | null = null; // 通过key比对是否有可以reuse const newKey = newChild.key; @@ -535,7 +537,7 @@ function diffObjectNodeHandler( } } - let resultNode = null; + let resultNode: VNode | null = null; let startDelVNode = firstChildVNode; if (newChild.vtype === TYPE_ELEMENT) { if (canReuseNode) { diff --git a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts index b23beb93..779f3653 100644 --- a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts +++ b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts @@ -186,11 +186,6 @@ function detachRef(vNode: VNode, isOldRef?: boolean) { // 卸载一个vNode,不会递归 function unmountVNode(vNode: VNode): void { - // TODO 暂时用于规避error处理逻辑,后续删除 - if (vNode.flags.Addition) { - return; - } - switch (vNode.tag) { case FunctionComponent: case ForwardRef: diff --git a/libs/horizon/src/renderer/vnode/VNodeFlags.ts b/libs/horizon/src/renderer/vnode/VNodeFlags.ts index 7259beb7..15e25131 100644 --- a/libs/horizon/src/renderer/vnode/VNodeFlags.ts +++ b/libs/horizon/src/renderer/vnode/VNodeFlags.ts @@ -19,7 +19,7 @@ export const ShouldCapture = 'ShouldCapture'; // For suspense export const ForceUpdate = 'ForceUpdate'; -const flagArr = [Addition, Update, Deletion, ResetText, Callback, DidCapture, Ref, Snapshot, Interrupted, ShouldCapture, ForceUpdate]; +const FlagArr = [Addition, Update, Deletion, ResetText, Callback, DidCapture, Ref, Snapshot, Interrupted, ShouldCapture, ForceUpdate]; const LifecycleEffectArr = [Update, Callback, Ref, Snapshot]; @@ -38,9 +38,10 @@ export class FlagUtils { } static hasAnyFlag(node: VNode) { // 有标志位 let keyFlag = false; - flagArr.forEach(key => { + FlagArr.forEach(key => { if (node.flags[key]) { keyFlag = true; + return; } }); return keyFlag; From ebf980b555c0cbebea364ca84ddabdf49aa70f39 Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 20 Jan 2022 15:59:54 +0800 Subject: [PATCH 04/11] Match-id-1335e3889f5c02b60f5677cb8bb35b2322310d6b --- libs/horizon/src/dom/SelectionRangeHandler.ts | 2 +- .../dom/valueHandler/ValueChangeHandler.ts | 6 +++--- libs/horizon/src/external/ChildrenUtil.ts | 1 - libs/horizon/src/renderer/TreeBuilder.ts | 4 ++-- libs/horizon/src/renderer/diff/DiffTools.ts | 2 +- .../src/renderer/hooks/UseEffectHook.ts | 4 ++-- .../src/renderer/render/LazyComponent.ts | 20 +++++++++---------- .../src/renderer/submit/LifeCycleHandler.ts | 4 ++-- libs/horizon/src/renderer/vnode/VNodeUtils.ts | 1 + 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/libs/horizon/src/dom/SelectionRangeHandler.ts b/libs/horizon/src/dom/SelectionRangeHandler.ts index d4f98942..8497984d 100644 --- a/libs/horizon/src/dom/SelectionRangeHandler.ts +++ b/libs/horizon/src/dom/SelectionRangeHandler.ts @@ -69,7 +69,7 @@ function isNodeContainsByTargetNode(targetNode, node) { return false; } -function isInDocument(dom) { +function isInDocument(dom): any | void { if (dom && dom.ownerDocument) { return isNodeContainsByTargetNode(dom.ownerDocument.documentElement, dom); } diff --git a/libs/horizon/src/dom/valueHandler/ValueChangeHandler.ts b/libs/horizon/src/dom/valueHandler/ValueChangeHandler.ts index 35d31e20..34406ef8 100644 --- a/libs/horizon/src/dom/valueHandler/ValueChangeHandler.ts +++ b/libs/horizon/src/dom/valueHandler/ValueChangeHandler.ts @@ -34,12 +34,12 @@ export function watchValueChange(dom) { // currentVal存储最新值,并重写value的setter、getter let currentVal = String(dom[keyForValue]); - const setFunc = descriptor.set; + const setFunc = descriptor?.set; Object.defineProperty(dom, keyForValue, { ...descriptor, - set: function (value) { + set: function(value) { currentVal = String(value); - setFunc.apply(this, [value]); + setFunc?.apply(this, [value]); }, }); diff --git a/libs/horizon/src/external/ChildrenUtil.ts b/libs/horizon/src/external/ChildrenUtil.ts index 179cf73e..9b35623f 100644 --- a/libs/horizon/src/external/ChildrenUtil.ts +++ b/libs/horizon/src/external/ChildrenUtil.ts @@ -47,7 +47,6 @@ function mapChildrenToArray( 'Object is invalid as a Horizon child. ' ); default: - return; } } diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts index a628048b..55edba61 100644 --- a/libs/horizon/src/renderer/TreeBuilder.ts +++ b/libs/horizon/src/renderer/TreeBuilder.ts @@ -219,7 +219,7 @@ export function tryRenderRoot(treeRoot: VNode) { } // 发起更新 -export function launchUpdateFromVNode(vNode: VNode): null | void { +export function launchUpdateFromVNode(vNode: VNode): void { // 检查循环调用 checkLoopingUpdateLimit(); @@ -228,7 +228,7 @@ export function launchUpdateFromVNode(vNode: VNode): null | void { if (treeRoot === null) { // 可能场景是:the componentWillUnmount method 或 useEffect cleanup function 方法中写异步任务,并且修改state。 // 因为异步回调的时候root都可能被清除了。 - return null; + return; } // 保存待刷新的节点 diff --git a/libs/horizon/src/renderer/diff/DiffTools.ts b/libs/horizon/src/renderer/diff/DiffTools.ts index 928f889b..c2b44411 100644 --- a/libs/horizon/src/renderer/diff/DiffTools.ts +++ b/libs/horizon/src/renderer/diff/DiffTools.ts @@ -6,7 +6,7 @@ export const isSameType = (vNode: VNode, ele: HorizonElement) => { return vNode.type === ele.type || (vNode.isLazyComponent && vNode.lazyType === ele.type); }; -export function createRef(element: HorizonElement) { +export function createRef(element: HorizonElement): any | void { const elementRef = element.ref; // 如果ref是null、function、object,直接返回 if (elementRef === null || typeof elementRef === 'function' || typeof elementRef === 'object') { diff --git a/libs/horizon/src/renderer/hooks/UseEffectHook.ts b/libs/horizon/src/renderer/hooks/UseEffectHook.ts index ccd4e7d4..d3418782 100644 --- a/libs/horizon/src/renderer/hooks/UseEffectHook.ts +++ b/libs/horizon/src/renderer/hooks/UseEffectHook.ts @@ -31,9 +31,9 @@ function useEffect( } if (stage === HookStage.Init) { - return useEffectForInit(effectFunc, deps, effectType); + useEffectForInit(effectFunc, deps, effectType); } else if (stage === HookStage.Update) { - return useEffectForUpdate(effectFunc, deps, effectType); + useEffectForUpdate(effectFunc, deps, effectType); } } diff --git a/libs/horizon/src/renderer/render/LazyComponent.ts b/libs/horizon/src/renderer/render/LazyComponent.ts index 65f7285c..99e35353 100644 --- a/libs/horizon/src/renderer/render/LazyComponent.ts +++ b/libs/horizon/src/renderer/render/LazyComponent.ts @@ -1,23 +1,23 @@ -import type {VNode} from '../Types'; +import type { VNode } from '../Types'; -import {FlagUtils} from '../vnode/VNodeFlags'; -import {getLazyVNodeTag} from '../vnode/VNodeCreator'; +import { FlagUtils } from '../vnode/VNodeFlags'; +import { getLazyVNodeTag } from '../vnode/VNodeCreator'; import { ClassComponent, ForwardRef, FunctionComponent, MemoComponent, } from '../vnode/VNodeTags'; -import {throwIfTrue} from '../utils/throwIfTrue'; -import {captureFunctionComponent} from './FunctionComponent'; -import {captureClassComponent} from './ClassComponent'; -import {captureMemoComponent} from './MemoComponent'; +import { throwIfTrue } from '../utils/throwIfTrue'; +import { captureFunctionComponent } from './FunctionComponent'; +import { captureClassComponent } from './ClassComponent'; +import { captureMemoComponent } from './MemoComponent'; export function captureRender(processing: VNode, shouldUpdate: boolean): VNode | null { return captureLazyComponent(processing, processing.type, shouldUpdate); } -export function bubbleRender() {} +export function bubbleRender() { } const LazyRendererMap = { [FunctionComponent]: captureFunctionComponent, @@ -30,7 +30,7 @@ function captureLazyComponent( processing, lazyComponent, shouldUpdate, -) { +): any | void { if (!processing.isCreated) { // 每次加载lazy都当作mount来处理 processing.isCreated = true; @@ -69,7 +69,7 @@ function captureLazyComponent( export function mergeDefaultProps(Component: any, props: object): object { if (Component && Component.defaultProps) { - const clonedProps = {...props}; + const clonedProps = { ...props }; const defaultProps = Component.defaultProps; Object.keys(defaultProps).forEach(key => { if (clonedProps[key] === undefined) { diff --git a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts index 779f3653..3c303192 100644 --- a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts +++ b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts @@ -70,8 +70,8 @@ function callBeforeSubmitLifeCycles( case TreeRoot: { const root = vNode.realNode; clearContainer(root.outerDom); - return; } + // No Default } } @@ -136,8 +136,8 @@ function callAfterSubmitLifeCycles( vNode.realNode.focus(); } } - return; } + //No Default } } diff --git a/libs/horizon/src/renderer/vnode/VNodeUtils.ts b/libs/horizon/src/renderer/vnode/VNodeUtils.ts index 0183dd67..3251cb7d 100644 --- a/libs/horizon/src/renderer/vnode/VNodeUtils.ts +++ b/libs/horizon/src/renderer/vnode/VNodeUtils.ts @@ -144,6 +144,7 @@ export function findDomVNode(vNode: VNode): VNode | null { if (node.tag === DomComponent || node.tag === DomText) { return node; } + return null; }); } From c66db8d84fc4a138b4478891354b37934f7a397a Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 20 Jan 2022 16:59:09 +0800 Subject: [PATCH 05/11] Match-id-8d8e589bdbc4b167a6b0e61f1706aa51e83543e5 --- libs/horizon/src/dom/SelectionRangeHandler.ts | 3 ++- libs/horizon/src/external/ChildrenUtil.ts | 3 ++- libs/horizon/src/renderer/TreeBuilder.ts | 2 +- libs/horizon/src/renderer/render/LazyComponent.ts | 3 ++- libs/horizon/src/renderer/submit/LifeCycleHandler.ts | 4 +++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/libs/horizon/src/dom/SelectionRangeHandler.ts b/libs/horizon/src/dom/SelectionRangeHandler.ts index 8497984d..f5bde5bf 100644 --- a/libs/horizon/src/dom/SelectionRangeHandler.ts +++ b/libs/horizon/src/dom/SelectionRangeHandler.ts @@ -69,10 +69,11 @@ function isNodeContainsByTargetNode(targetNode, node) { return false; } -function isInDocument(dom): any | void { +function isInDocument(dom) { if (dom && dom.ownerDocument) { return isNodeContainsByTargetNode(dom.ownerDocument.documentElement, dom); } + return false; } // 判断一个标签是否有设置选择范围的能力 diff --git a/libs/horizon/src/external/ChildrenUtil.ts b/libs/horizon/src/external/ChildrenUtil.ts index 9b35623f..d46e217b 100644 --- a/libs/horizon/src/external/ChildrenUtil.ts +++ b/libs/horizon/src/external/ChildrenUtil.ts @@ -46,7 +46,8 @@ function mapChildrenToArray( throw new Error( 'Object is invalid as a Horizon child. ' ); - default: + + // No Default } } diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts index 55edba61..0d3bfa9e 100644 --- a/libs/horizon/src/renderer/TreeBuilder.ts +++ b/libs/horizon/src/renderer/TreeBuilder.ts @@ -219,7 +219,7 @@ export function tryRenderRoot(treeRoot: VNode) { } // 发起更新 -export function launchUpdateFromVNode(vNode: VNode): void { +export function launchUpdateFromVNode(vNode: VNode) { // 检查循环调用 checkLoopingUpdateLimit(); diff --git a/libs/horizon/src/renderer/render/LazyComponent.ts b/libs/horizon/src/renderer/render/LazyComponent.ts index 99e35353..f3dcc954 100644 --- a/libs/horizon/src/renderer/render/LazyComponent.ts +++ b/libs/horizon/src/renderer/render/LazyComponent.ts @@ -30,7 +30,7 @@ function captureLazyComponent( processing, lazyComponent, shouldUpdate, -): any | void { +) { if (!processing.isCreated) { // 每次加载lazy都当作mount来处理 processing.isCreated = true; @@ -64,6 +64,7 @@ function captureLazyComponent( Component, '', ); + return null; } } diff --git a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts index 3c303192..d2eafa4c 100644 --- a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts +++ b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts @@ -71,6 +71,7 @@ function callBeforeSubmitLifeCycles( const root = vNode.realNode; clearContainer(root.outerDom); } + // No Default } } @@ -137,7 +138,8 @@ function callAfterSubmitLifeCycles( } } } - //No Default + + // No Default } } From 10c8ee378680945c0b76ff6800c1f68184ad0b7e Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 20 Jan 2022 18:14:44 +0800 Subject: [PATCH 06/11] Match-id-812ccb8a377f3e7902c60846555b0050c1f6d8bd --- libs/horizon/src/external/ChildrenUtil.ts | 81 +++++---- libs/horizon/src/external/HorizonElement.ts | 18 +- libs/horizon/src/renderer/Renderer.ts | 2 +- .../src/renderer/render/BaseComponent.ts | 51 +++--- .../src/renderer/render/ClassComponent.ts | 162 +++++++++--------- .../src/renderer/render/ClsOrFunComponent.ts | 14 +- .../src/renderer/render/ContextConsumer.ts | 13 +- .../src/renderer/render/ContextProvider.ts | 16 +- 8 files changed, 178 insertions(+), 179 deletions(-) diff --git a/libs/horizon/src/external/ChildrenUtil.ts b/libs/horizon/src/external/ChildrenUtil.ts index d46e217b..c1b681ad 100644 --- a/libs/horizon/src/external/ChildrenUtil.ts +++ b/libs/horizon/src/external/ChildrenUtil.ts @@ -12,45 +12,6 @@ function getItemKey(item: any, index: number): string { return '.' + index.toString(36); } -function mapChildrenToArray( - children: any, - arr: Array, - prefix: string, - callback?: Function, -): number | void { - const type = typeof children; - switch (type) { - // 继承原有规格,undefined和boolean类型按照null处理 - case 'undefined': - case 'boolean': - callMapFun(null, arr, prefix, callback); - return; - case 'number': - case 'string': - callMapFun(children, arr, prefix, callback); - return; - case 'object': - if (children === null) { - callMapFun(null, arr, prefix, callback); - return; - } - const vtype = children.vtype; - if (vtype === TYPE_ELEMENT || vtype === TYPE_PORTAL) { - callMapFun(children, arr, prefix, callback) ; - return; - } - if (Array.isArray(children)) { - processArrayChildren(children, arr, prefix, callback); - return; - } - throw new Error( - 'Object is invalid as a Horizon child. ' - ); - - // No Default - } -} - function processArrayChildren( children: any, arr: Array, @@ -100,6 +61,44 @@ function callMapFun( } } +function mapChildrenToArray( + children: any, + arr: Array, + prefix: string, + callback?: Function, +): number | void { + const type = typeof children; + switch (type) { + // 继承原有规格,undefined和boolean类型按照null处理 + case 'undefined': + case 'boolean': + callMapFun(null, arr, prefix, callback); + return; + case 'number': + case 'string': + callMapFun(children, arr, prefix, callback); + return; + case 'object': + if (children === null) { + callMapFun(null, arr, prefix, callback); + return; + } + const vtype = children.vtype; + if (vtype === TYPE_ELEMENT || vtype === TYPE_PORTAL) { + callMapFun(children, arr, prefix, callback) ; + return; + } + if (Array.isArray(children)) { + processArrayChildren(children, arr, prefix, callback); + return; + } + throw new Error( + 'Object is invalid as a Horizon child. ' + ); + // No Default + } +} + // 在 children 里的每个直接子节点上调用一个函数,并将 this 设置为 thisArg function mapChildren( children: any, @@ -111,9 +110,7 @@ function mapChildren( } let count = 0; const result = []; - mapChildrenToArray(children, result, '', (child) => { - return func.call(context, child, count++); - }); + mapChildrenToArray(children, result, '', child => func.call(context, child, count++)); return result; } diff --git a/libs/horizon/src/external/HorizonElement.ts b/libs/horizon/src/external/HorizonElement.ts index 186f343c..c315bd6a 100644 --- a/libs/horizon/src/external/HorizonElement.ts +++ b/libs/horizon/src/external/HorizonElement.ts @@ -23,12 +23,20 @@ export function HorizonElement(type, key, ref, vNode, props) { // 记录负责创建此元素的组件。 _vNode: vNode, }; -}; +} function isValidKey(key) { return key !== 'key' && key !== 'ref' && key !== '__source'; } +function mergeDefault(sourceObj, defaultObj) { + Object.keys(defaultObj).forEach((key) => { + if (sourceObj[key] === undefined) { + sourceObj[key] = defaultObj[key]; + } + }); +} + function buildElement(isClone, type, setting, ...children) { // setting中的值优先级最高,clone情况下从 type 中取值,创建情况下直接赋值为 null const key = (setting && setting.key !== undefined) ? String(setting.key) : (isClone ? type.key : null); @@ -64,14 +72,6 @@ export function createElement(type, setting, ...children) { return buildElement(false, type, setting, ...children); } -function mergeDefault(sourceObj, defaultObj) { - Object.keys(defaultObj).forEach((key) => { - if (sourceObj[key] === undefined) { - sourceObj[key] = defaultObj[key]; - } - }); -} - export function cloneElement(element, setting, ...children) { return buildElement(true, element, setting, ...children); } diff --git a/libs/horizon/src/renderer/Renderer.ts b/libs/horizon/src/renderer/Renderer.ts index ebf8da2b..32c3cc0f 100644 --- a/libs/horizon/src/renderer/Renderer.ts +++ b/libs/horizon/src/renderer/Renderer.ts @@ -37,7 +37,7 @@ export function startUpdate( launchUpdateFromVNode(treeRoot); } -export function getFirstCustomDom(treeRoot: VNode): Element | Text | null { +export function getFirstCustomDom(treeRoot: VNode | undefined | null): Element | Text | null { if (treeRoot?.child) { return treeRoot.child.realNode; } diff --git a/libs/horizon/src/renderer/render/BaseComponent.ts b/libs/horizon/src/renderer/render/BaseComponent.ts index b045b215..8e5e56d4 100644 --- a/libs/horizon/src/renderer/render/BaseComponent.ts +++ b/libs/horizon/src/renderer/render/BaseComponent.ts @@ -15,6 +15,32 @@ import { createChildrenByDiff } from '../diff/nodeDiffComparator'; import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator'; import componentRenders from './index'; +// 复用vNode时,也需对stack进行处理 +function handlerContext(processing: VNode) { + switch (processing.tag) { + case TreeRoot: + setNamespaceCtx(processing, processing.outerDom); + break; + case DomComponent: + setNamespaceCtx(processing); + break; + case ClassComponent: { + const isOldCxtExist = isOldProvider(processing.type); + cacheOldCtx(processing, isOldCxtExist); + break; + } + case DomPortal: + setNamespaceCtx(processing, processing.outerDom); + break; + case ContextProvider: { + const newValue = processing.props.value; + setContextCtx(processing, newValue); + break; + } + // No Default + } +} + export function captureVNode(processing: VNode): VNode | null { const component = componentRenders[processing.tag]; @@ -38,31 +64,6 @@ export function captureVNode(processing: VNode): VNode | null { return component.captureRender(processing, shouldUpdate); } -// 复用vNode时,也需对stack进行处理 -function handlerContext(processing: VNode) { - switch (processing.tag) { - case TreeRoot: - setNamespaceCtx(processing, processing.outerDom); - break; - case DomComponent: - setNamespaceCtx(processing); - break; - case ClassComponent: { - const isOldCxtExist = isOldProvider(processing.type); - cacheOldCtx(processing, isOldCxtExist); - break; - } - case DomPortal: - setNamespaceCtx(processing, processing.outerDom); - break; - case ContextProvider: { - const newValue = processing.props.value; - setContextCtx(processing, newValue); - break; - } - } -} - // 创建孩子节点 export function createVNodeChildren(processing: VNode, nextChildren: any) { const isComparing = !processing.isCreated; diff --git a/libs/horizon/src/renderer/render/ClassComponent.ts b/libs/horizon/src/renderer/render/ClassComponent.ts index 72ff9224..2be13d49 100644 --- a/libs/horizon/src/renderer/render/ClassComponent.ts +++ b/libs/horizon/src/renderer/render/ClassComponent.ts @@ -20,7 +20,7 @@ import { markComponentDidUpdate, markGetSnapshotBeforeUpdate, } from './class/ClassLifeCycleProcessor'; -import {FlagUtils} from '../vnode/VNodeFlags'; +import { FlagUtils } from '../vnode/VNodeFlags'; import { createVNodeChildren, markRef } from './BaseComponent'; import { createUpdateArray, @@ -28,25 +28,80 @@ import { } from '../UpdateHandler'; import { getContextChangeCtx, setContextChangeCtx } from '../ContextSaver'; import ProcessingVNode from '../vnode/ProcessingVNode'; -import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator'; +import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator'; -export function captureRender(processing: VNode): VNode | null { - const clazz = processing.type; - const props = processing.props; - const nextProps = processing.isLazyComponent ? mergeDefaultProps(clazz, props) : props; - return captureClassComponent(processing, clazz, nextProps); +// 获取当前节点的context +export function getCurrentContext(clazz, processing: VNode) { + const context = clazz.contextType; + return typeof context === 'object' && context !== null + ? getNewContext(processing, context) + : getOldContext(processing, clazz, true); } -export function bubbleRender(processing: VNode) { - if (isOldProvider(processing.type)) { - resetOldCtx(processing); +// 挂载实例 +function mountInstance(clazz, processing: VNode, nextProps: object) { + if (!processing.isCreated) { + processing.isCreated = true; + FlagUtils.markAddition(processing); + } + + // 构造实例 + callConstructor(processing, clazz, nextProps); + + const inst = processing.realNode; + inst.props = nextProps; + inst.state = processing.state; + inst.context = getCurrentContext(clazz, processing); + inst.refs = {}; + + createUpdateArray(processing); + processUpdates(processing, inst, nextProps); + inst.state = processing.state; + + // 在调用类组建的渲染方法之前调用 并且在初始挂载及后续更新时都会被调用 + callDerivedStateFromProps(processing, clazz.getDerivedStateFromProps, nextProps); + callComponentWillMount(processing, inst, nextProps); + + markComponentDidMount(processing); +} + +// 构建子节点 +function createChildren(clazz: any, processing: VNode) { + markRef(processing); + + ProcessingVNode.val = processing; + processing.state = processing.realNode.state; + + const inst = processing.realNode; + const isCatchError = processing.flags.DidCapture; + + // 按照已有规格,如果捕获了错误却没有定义getDerivedStateFromError函数,返回的child为null + const newElements = isCatchError && typeof clazz.getDerivedStateFromError !== 'function' + ? null + : inst.render(); + + processing.child = createVNodeChildren(processing, newElements); + return processing.child; +} + +// 根据isUpdateComponent,执行不同的生命周期 +function callUpdateLifeCycle(processing: VNode, nextProps: object, clazz) { + const inst = processing.realNode; + const newContext = getCurrentContext(clazz, processing); + if (processing.isCreated) { + callComponentWillMount(processing, inst); + } else { + callComponentWillUpdate(inst, nextProps, processing.state, newContext); } } -// 用于未完成的类组件 -export function getIncompleteClassComponent(clazz, processing: VNode, nextProps: object):VNode | null { - mountInstance(clazz, processing, nextProps); - return createChildren(clazz, processing); +function markLifeCycle(processing: VNode, nextProps: object, shouldUpdate: Boolean) { + if (processing.isCreated) { + markComponentDidMount(processing); + } else if (processing.state !== processing.oldState || shouldUpdate) { + markComponentDidUpdate(processing); + markGetSnapshotBeforeUpdate(processing); + } } // 用于类组件 @@ -127,76 +182,21 @@ export function captureClassComponent(processing: VNode, clazz: any, nextProps: } } -// 挂载实例 -function mountInstance(clazz, processing: VNode, nextProps: object) { - if (!processing.isCreated) { - processing.isCreated = true; - FlagUtils.markAddition(processing); - } - - // 构造实例 - callConstructor(processing, clazz, nextProps); - - const inst = processing.realNode; - inst.props = nextProps; - inst.state = processing.state; - inst.context = getCurrentContext(clazz, processing); - inst.refs = {}; - - createUpdateArray(processing); - processUpdates(processing, inst, nextProps); - inst.state = processing.state; - - // 在调用类组建的渲染方法之前调用 并且在初始挂载及后续更新时都会被调用 - callDerivedStateFromProps(processing, clazz.getDerivedStateFromProps, nextProps); - callComponentWillMount(processing, inst, nextProps); - - markComponentDidMount(processing); +export function captureRender(processing: VNode): VNode | null { + const clazz = processing.type; + const props = processing.props; + const nextProps = processing.isLazyComponent ? mergeDefaultProps(clazz, props) : props; + return captureClassComponent(processing, clazz, nextProps); } -// 构建子节点 -function createChildren(clazz: any, processing: VNode) { - markRef(processing); - - ProcessingVNode.val = processing; - processing.state = processing.realNode.state; - - const inst = processing.realNode; - const isCatchError = processing.flags.DidCapture; - - // 按照已有规格,如果捕获了错误却没有定义getDerivedStateFromError函数,返回的child为null - const newElements = (isCatchError && typeof clazz.getDerivedStateFromError !== 'function') - ? null - : inst.render(); - - processing.child = createVNodeChildren(processing, newElements); - return processing.child; -} - -// 获取当前节点的context -export function getCurrentContext(clazz, processing: VNode) { - const context = clazz.contextType; - return typeof context === 'object' && context !== null - ? getNewContext(processing, context) - : getOldContext(processing, clazz, true); -} - -// 根据isUpdateComponent,执行不同的生命周期 -function callUpdateLifeCycle(processing: VNode, nextProps: object, clazz) { - const inst = processing.realNode; - const newContext = getCurrentContext(clazz, processing); - if (processing.isCreated) { - callComponentWillMount(processing, inst); - } else { - callComponentWillUpdate(inst, nextProps, processing.state, newContext); +export function bubbleRender(processing: VNode) { + if (isOldProvider(processing.type)) { + resetOldCtx(processing); } } -function markLifeCycle(processing: VNode, nextProps: object, shouldUpdate: Boolean) { - if (processing.isCreated) { - markComponentDidMount(processing); - } else if (processing.state !== processing.oldState || shouldUpdate) { - markComponentDidUpdate(processing); - markGetSnapshotBeforeUpdate(processing); - } +// 用于未完成的类组件 +export function getIncompleteClassComponent(clazz, processing: VNode, nextProps: object): VNode | null { + mountInstance(clazz, processing, nextProps); + return createChildren(clazz, processing); } diff --git a/libs/horizon/src/renderer/render/ClsOrFunComponent.ts b/libs/horizon/src/renderer/render/ClsOrFunComponent.ts index 4a2204a1..6eccd85b 100644 --- a/libs/horizon/src/renderer/render/ClsOrFunComponent.ts +++ b/libs/horizon/src/renderer/render/ClsOrFunComponent.ts @@ -7,14 +7,8 @@ import {FlagUtils} from '../vnode/VNodeFlags'; import {exeFunctionHook} from '../hooks/HookMain'; import {createVNodeChildren} from './BaseComponent'; -export function captureRender(processing: VNode): VNode | null { - return captureIndeterminateComponent(processing); -} - -export function bubbleRender() {} - function captureIndeterminateComponent( - processing: VNode | null, + processing: VNode, ): VNode | null { const funcComp = processing.type; @@ -34,3 +28,9 @@ function captureIndeterminateComponent( processing.child = createVNodeChildren(processing, newElements); return processing.child; } + +export function captureRender(processing: VNode): VNode | null { + return captureIndeterminateComponent(processing); +} + +export function bubbleRender() {} diff --git a/libs/horizon/src/renderer/render/ContextConsumer.ts b/libs/horizon/src/renderer/render/ContextConsumer.ts index 7fcbd59c..c45934db 100644 --- a/libs/horizon/src/renderer/render/ContextConsumer.ts +++ b/libs/horizon/src/renderer/render/ContextConsumer.ts @@ -3,12 +3,6 @@ import type {VNode, ContextType} from '../Types'; import {resetDepContexts, getNewContext} from '../components/context/Context'; import {createVNodeChildren} from './BaseComponent'; -export function captureRender(processing: VNode): VNode | null { - return captureContextConsumer(processing); -} - -export function bubbleRender() {} - function captureContextConsumer(processing: VNode) { const context: ContextType = processing.type; const props = processing.props; @@ -21,3 +15,10 @@ function captureContextConsumer(processing: VNode) { processing.child = createVNodeChildren(processing, newChildren); return processing.child; } + +export function captureRender(processing: VNode): VNode | null { + return captureContextConsumer(processing); +} + +export function bubbleRender() {} + diff --git a/libs/horizon/src/renderer/render/ContextProvider.ts b/libs/horizon/src/renderer/render/ContextProvider.ts index c0ef07e6..c60156c7 100644 --- a/libs/horizon/src/renderer/render/ContextProvider.ts +++ b/libs/horizon/src/renderer/render/ContextProvider.ts @@ -14,14 +14,6 @@ import {launchUpdateFromVNode} from '../TreeBuilder'; import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator'; import {setParentsChildShouldUpdate} from '../vnode/VNodeShouldUpdate'; -export function captureRender(processing: VNode): VNode | null { - return captureContextProvider(processing); -} - -export function bubbleRender(processing: VNode) { - resetContextCtx(processing); -} - function captureContextProvider(processing: VNode): VNode | null { const providerType: ProviderType = processing.type; const contextType: ContextType = providerType._context; @@ -54,6 +46,14 @@ function captureContextProvider(processing: VNode): VNode | null { return processing.child; } +export function captureRender(processing: VNode): VNode | null { + return captureContextProvider(processing); +} + +export function bubbleRender(processing: VNode) { + resetContextCtx(processing); +} + // 从依赖中找到匹配context的VNode function matchDependencies(depContexts, context, vNode): boolean { for (let i = 0; i < depContexts.length; i++) { From 25c3a84991bd95fdd406026bf921a3212371dfcb Mon Sep 17 00:00:00 2001 From: * <8> Date: Fri, 21 Jan 2022 10:00:29 +0800 Subject: [PATCH 07/11] Match-id-dda58446000d7d98ad097c8e72465ec8e13cfe58 --- libs/horizon/src/renderer/Renderer.ts | 2 +- .../src/renderer/render/ContextProvider.ts | 52 +++++++++---------- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/libs/horizon/src/renderer/Renderer.ts b/libs/horizon/src/renderer/Renderer.ts index 32c3cc0f..d5cf89da 100644 --- a/libs/horizon/src/renderer/Renderer.ts +++ b/libs/horizon/src/renderer/Renderer.ts @@ -37,7 +37,7 @@ export function startUpdate( launchUpdateFromVNode(treeRoot); } -export function getFirstCustomDom(treeRoot: VNode | undefined | null): Element | Text | null { +export function getFirstCustomDom(treeRoot?: VNode | null): Element | Text | null { if (treeRoot?.child) { return treeRoot.child.realNode; } diff --git a/libs/horizon/src/renderer/render/ContextProvider.ts b/libs/horizon/src/renderer/render/ContextProvider.ts index c60156c7..b980273c 100644 --- a/libs/horizon/src/renderer/render/ContextProvider.ts +++ b/libs/horizon/src/renderer/render/ContextProvider.ts @@ -14,6 +14,32 @@ import {launchUpdateFromVNode} from '../TreeBuilder'; import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator'; import {setParentsChildShouldUpdate} from '../vnode/VNodeShouldUpdate'; +// 从当前子节点开始向下遍历,找到消费此context的组件,并更新 +function handleContextChange(processing: VNode, context: ContextType): void { + const vNode = processing.child; + if (vNode === null) { + return; + } + + let isMatch = false; + + // 从vNode开始遍历 + travelVNodeTree(vNode, (node) => { + const depContexts = node.depContexts; + if (depContexts.length) { + isMatch = matchDependencies(depContexts, context, node) ?? isMatch; + } + }, (node) => { + // 如果这是匹配的provider,则不要更深入地扫描 + return node.tag === ContextProvider && node.type === processing.type; + }, processing); + + // 找到了依赖context的子节点,触发一次更新 + if (isMatch) { + launchUpdateFromVNode(processing); + } +} + function captureContextProvider(processing: VNode): VNode | null { const providerType: ProviderType = processing.type; const contextType: ContextType = providerType._context; @@ -77,29 +103,3 @@ function matchDependencies(depContexts, context, vNode): boolean { return false; } - -// 从当前子节点开始向下遍历,找到消费此context的组件,并更新 -function handleContextChange(processing: VNode, context: ContextType): void { - const vNode = processing.child; - if (vNode === null) { - return; - } - - let isMatch = false; - - // 从vNode开始遍历 - travelVNodeTree(vNode, (node) => { - const depContexts = node.depContexts; - if (depContexts.length) { - isMatch = matchDependencies(depContexts, context, node) ?? isMatch; - } - }, (node) => { - // 如果这是匹配的provider,则不要更深入地扫描 - return node.tag === ContextProvider && node.type === processing.type; - }, processing); - - // 找到了依赖context的子节点,触发一次更新 - if (isMatch) { - launchUpdateFromVNode(processing); - } -} From bd5b63e78604d0ca17beb65f7470bda9d03dd409 Mon Sep 17 00:00:00 2001 From: * <8> Date: Fri, 21 Jan 2022 10:03:41 +0800 Subject: [PATCH 08/11] Match-id-fcb0cfba817783aae4dfdfdd77abe43a0b922db4 --- libs/horizon/src/dom/DOMInternalKeys.ts | 9 ++-- libs/horizon/src/external/ChildrenUtil.ts | 10 ++--- libs/horizon/src/external/Horizon.ts | 4 +- .../{HorizonElement.ts => JSXElement.ts} | 30 ++++++------- .../JSXElementType.ts} | 2 +- libs/horizon/src/renderer/ExecuteMode.ts | 2 +- libs/horizon/src/renderer/GlobalVar.ts | 34 +++++++++++++++ libs/horizon/src/renderer/TreeBuilder.ts | 43 ++++++------------- libs/horizon/src/renderer/Types.ts | 4 +- .../src/renderer/components/CreatePortal.ts | 2 +- .../src/renderer/components/ForwardRef.ts | 2 +- libs/horizon/src/renderer/components/Lazy.ts | 2 +- libs/horizon/src/renderer/components/Memo.ts | 2 +- .../components/context/CreateContext.ts | 2 +- libs/horizon/src/renderer/diff/DiffTools.ts | 20 +-------- .../src/renderer/diff/nodeDiffComparator.ts | 25 ++++++----- .../src/renderer/render/ClassComponent.ts | 7 +-- .../src/renderer/render/MemoComponent.ts | 2 +- .../src/renderer/submit/LifeCycleHandler.ts | 20 +++++++-- libs/horizon/src/renderer/submit/Submit.ts | 3 +- .../src/renderer/vnode/ProcessingVNode.ts | 8 ---- libs/horizon/src/renderer/vnode/VNode.ts | 2 + .../src/renderer/vnode/VNodeCreator.ts | 6 +-- libs/horizon/src/renderer/vnode/VNodeUtils.ts | 10 ++--- 24 files changed, 131 insertions(+), 120 deletions(-) rename libs/horizon/src/external/{HorizonElement.ts => JSXElement.ts} (70%) rename libs/horizon/src/{renderer/utils/elementType.ts => external/JSXElementType.ts} (89%) create mode 100644 libs/horizon/src/renderer/GlobalVar.ts delete mode 100644 libs/horizon/src/renderer/vnode/ProcessingVNode.ts diff --git a/libs/horizon/src/dom/DOMInternalKeys.ts b/libs/horizon/src/dom/DOMInternalKeys.ts index 3b59dd2d..dec6fed1 100644 --- a/libs/horizon/src/dom/DOMInternalKeys.ts +++ b/libs/horizon/src/dom/DOMInternalKeys.ts @@ -14,14 +14,13 @@ import { TreeRoot, } from '../renderer/vnode/VNodeTags'; -const suffixKey = new Date().getTime().toString(); const prefix = '_horizon'; const internalKeys = { - VNode: `${prefix}VNode@${suffixKey}`, - props: `${prefix}Props@${suffixKey}`, - events: `${prefix}Events@${suffixKey}`, - nonDelegatedEvents: `${prefix}NonDelegatedEvents@${suffixKey}`, + VNode: `${prefix}VNode`, + props: `${prefix}Props`, + events: `${prefix}Events`, + nonDelegatedEvents: `${prefix}NonDelegatedEvents`, }; // 通过 VNode 实例获取 DOM 节点 diff --git a/libs/horizon/src/external/ChildrenUtil.ts b/libs/horizon/src/external/ChildrenUtil.ts index 179cf73e..730a4059 100644 --- a/libs/horizon/src/external/ChildrenUtil.ts +++ b/libs/horizon/src/external/ChildrenUtil.ts @@ -1,7 +1,7 @@ import {throwIfTrue} from '../renderer/utils/throwIfTrue'; -import {TYPE_ELEMENT, TYPE_PORTAL} from '../renderer/utils/elementType'; +import {TYPE_COMMON_ELEMENT, TYPE_PORTAL} from './JSXElementType'; -import {isValidElement, HorizonElement} from './HorizonElement'; +import {isValidElement, JSXElement} from './JSXElement'; // 生成key function getItemKey(item: any, index: number): string { @@ -35,7 +35,7 @@ function mapChildrenToArray( return; } const vtype = children.vtype; - if (vtype === TYPE_ELEMENT || vtype === TYPE_PORTAL) { + if (vtype === TYPE_COMMON_ELEMENT || vtype === TYPE_PORTAL) { callMapFun(children, arr, prefix, callback) ; return; } @@ -88,11 +88,11 @@ function callMapFun( ? '.$' + mappedChild.key : ''); // 返回一个修改key的children - mappedChild = HorizonElement( + mappedChild = JSXElement( mappedChild.type, newKey, mappedChild.ref, - mappedChild._vNode, + mappedChild.belongClassVNode, mappedChild.props, ); } diff --git a/libs/horizon/src/external/Horizon.ts b/libs/horizon/src/external/Horizon.ts index 8c933c80..623bfbb4 100644 --- a/libs/horizon/src/external/Horizon.ts +++ b/libs/horizon/src/external/Horizon.ts @@ -3,7 +3,7 @@ import { TYPE_PROFILER, TYPE_STRICT_MODE, TYPE_SUSPENSE, -} from '../renderer/utils/elementType'; +} from './JSXElementType'; import {Component, PureComponent} from '../renderer/components/BaseClassComponent'; import {createRef} from '../renderer/components/CreateRef'; @@ -12,7 +12,7 @@ import { createElement, cloneElement, isValidElement, -} from './HorizonElement'; +} from './JSXElement'; import {createContext} from '../renderer/components/context/CreateContext'; import {lazy} from '../renderer/components/Lazy'; import {forwardRef} from '../renderer/components/ForwardRef'; diff --git a/libs/horizon/src/external/HorizonElement.ts b/libs/horizon/src/external/JSXElement.ts similarity index 70% rename from libs/horizon/src/external/HorizonElement.ts rename to libs/horizon/src/external/JSXElement.ts index 186f343c..2edf86a3 100644 --- a/libs/horizon/src/external/HorizonElement.ts +++ b/libs/horizon/src/external/JSXElement.ts @@ -1,18 +1,18 @@ -import { TYPE_ELEMENT } from '../renderer/utils/elementType'; -import ProcessingVNode from '../renderer/vnode/ProcessingVNode'; +import { TYPE_COMMON_ELEMENT } from './JSXElementType'; +import { getProcessingClassVNode } from '../renderer/GlobalVar'; /** - * vtype, 节点的类型,这里固定是element - * type,保存dom节点的名称或者组件的函数地址 + * vtype 节点的类型,这里固定是element + * type 保存dom节点的名称或者组件的函数地址 * key key属性 * ref ref属性 * props 其他常规属性 */ -export function HorizonElement(type, key, ref, vNode, props) { +export function JSXElement(type, key, ref, vNode, props) { return { - // Horizon元素标识符 - vtype: TYPE_ELEMENT, + // 元素标识符 + vtype: TYPE_COMMON_ELEMENT, // 属于元素的内置属性 type: type, @@ -20,8 +20,8 @@ export function HorizonElement(type, key, ref, vNode, props) { ref: ref, props: props, - // 记录负责创建此元素的组件。 - _vNode: vNode, + // 所属的class组件 + belongClassVNode: vNode, }; }; @@ -34,7 +34,7 @@ function buildElement(isClone, type, setting, ...children) { const key = (setting && setting.key !== undefined) ? String(setting.key) : (isClone ? type.key : null); const ref = (setting && setting.ref !== undefined) ? setting.ref : (isClone ? type.ref : null); const props = isClone ? {...type.props} : {}; - let vNode = isClone ? type._vNode : ProcessingVNode.val; + let vNode = isClone ? type.belongClassVNode : getProcessingClassVNode(); if (setting != null) { Object.keys(setting).forEach(k => { @@ -43,7 +43,7 @@ function buildElement(isClone, type, setting, ...children) { } }); if (setting.ref !== undefined && isClone) { - vNode = ProcessingVNode.val; + vNode = getProcessingClassVNode(); } } @@ -51,15 +51,15 @@ function buildElement(isClone, type, setting, ...children) { props.children = children.length === 1 ? children[0] : children; } const element = isClone ? type.type : type; - //合并默认属性 + // 合并默认属性 if (element && element.defaultProps) { mergeDefault(props, element.defaultProps); } - return HorizonElement(element, key, ref, vNode, props); + return JSXElement(element, key, ref, vNode, props); } -//创建Element结构体,供JSX编译时调用 +// 创建Element结构体,供JSX编译时调用 export function createElement(type, setting, ...children) { return buildElement(false, type, setting, ...children); } @@ -78,5 +78,5 @@ export function cloneElement(element, setting, ...children) { // 检测结构体是否为合法的Element export function isValidElement(element) { - return !!(element && element.vtype === TYPE_ELEMENT); + return !!(element && element.vtype === TYPE_COMMON_ELEMENT); } diff --git a/libs/horizon/src/renderer/utils/elementType.ts b/libs/horizon/src/external/JSXElementType.ts similarity index 89% rename from libs/horizon/src/renderer/utils/elementType.ts rename to libs/horizon/src/external/JSXElementType.ts index b23ad4d6..b692f39a 100644 --- a/libs/horizon/src/renderer/utils/elementType.ts +++ b/libs/horizon/src/external/JSXElementType.ts @@ -1,4 +1,4 @@ -export const TYPE_ELEMENT = 1; +export const TYPE_COMMON_ELEMENT = 1; export const TYPE_PORTAL = 2; export const TYPE_FRAGMENT = 3; export const TYPE_STRICT_MODE = 4; diff --git a/libs/horizon/src/renderer/ExecuteMode.ts b/libs/horizon/src/renderer/ExecuteMode.ts index 880e9374..68c6f2c1 100644 --- a/libs/horizon/src/renderer/ExecuteMode.ts +++ b/libs/horizon/src/renderer/ExecuteMode.ts @@ -5,7 +5,7 @@ export const InRender = 'IN_RENDER'; type RenderMode = typeof ByAsync | typeof BySync | typeof InRender; -// 当前执行阶段标记 +// 当前执行模式标记 let executeMode = { [ByAsync]: false, [BySync]: false, diff --git a/libs/horizon/src/renderer/GlobalVar.ts b/libs/horizon/src/renderer/GlobalVar.ts new file mode 100644 index 00000000..e7ea335c --- /dev/null +++ b/libs/horizon/src/renderer/GlobalVar.ts @@ -0,0 +1,34 @@ +import type {VNode} from './Types'; + +// 当前处理的classVNode,用于inst.refs用法中的 +let processingClassVNode: VNode | null = null; +export function getProcessingClassVNode(): VNode | null { + return processingClassVNode; +} +export function setProcessingClassVNode(vNode: VNode | null) { + processingClassVNode = vNode; +} + +// 计算出来的刷新节点,不一定是根节点 +let startVNode: VNode | null = null; +export function getStartVNode(): VNode | null { + return startVNode; +} +export function setStartVNode(vNode: VNode | null) { + startVNode = vNode; +} + +type BuildVNodeResult = 0 | 1 | 2 | 3; +export const BuildInComplete = 0; +export const BuildFatalErrored = 1; +export const BuildErrored = 2; +export const BuildCompleted = 3; +// 根节点退出build tree时的状态,如: completed, incomplete, errored, fatalErrored. +let buildVNodeResult: BuildVNodeResult = BuildInComplete; +export function setBuildResult(result: BuildVNodeResult) { + buildVNodeResult = result; +} + +export function getBuildResult(): BuildVNodeResult { + return buildVNodeResult; +} diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts index a628048b..0abf20d0 100644 --- a/libs/horizon/src/renderer/TreeBuilder.ts +++ b/libs/horizon/src/renderer/TreeBuilder.ts @@ -9,7 +9,15 @@ 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 { + BuildCompleted, BuildErrored, + BuildFatalErrored, + BuildInComplete, getBuildResult, + getStartVNode, + setBuildResult, + setProcessingClassVNode, + setStartVNode +} from './GlobalVar'; import { findDomParent, getSiblingVNode } from './vnode/VNodeUtils'; import { ByAsync, @@ -28,39 +36,16 @@ import { 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; -} - // 为重新进行深度遍历做准备 function resetProcessingVariables(startUpdateVNode: VNode) { // 创建processing @@ -141,7 +126,6 @@ function buildVNodeTree(treeRoot: VNode) { // 计算出开始节点 const startUpdateVNode = calcStartUpdateVNode(treeRoot); - // 缓存起来 setStartVNode(startUpdateVNode); @@ -163,7 +147,7 @@ function buildVNodeTree(treeRoot: VNode) { recoverParentsContextCtx(startUpdateVNode); } - // 重置环境变量 + // 重置环境变量,为重新进行深度遍历做准备 resetProcessingVariables(startUpdateVNode); do { @@ -178,9 +162,10 @@ function buildVNodeTree(treeRoot: VNode) { } else { processing = next; } - - ProcessingVNode.val = null; } + + setProcessingClassVNode(null); + break; } catch (thrownValue) { handleError(treeRoot, thrownValue); diff --git a/libs/horizon/src/renderer/Types.ts b/libs/horizon/src/renderer/Types.ts index 2f3fad4b..34640d51 100644 --- a/libs/horizon/src/renderer/Types.ts +++ b/libs/horizon/src/renderer/Types.ts @@ -15,14 +15,14 @@ export type UseReducerHookType = { }; export type UseContextHookType = { useContext(context: ContextType,): T }; -export type HorizonElement = { +export type JSXElement = { vtype: any, type: any, key: any, ref: any, props: any, - _vNode: any, + belongClassVNode: any, }; export type ProviderType = { diff --git a/libs/horizon/src/renderer/components/CreatePortal.ts b/libs/horizon/src/renderer/components/CreatePortal.ts index 3bdb4b17..2b018f16 100644 --- a/libs/horizon/src/renderer/components/CreatePortal.ts +++ b/libs/horizon/src/renderer/components/CreatePortal.ts @@ -1,4 +1,4 @@ -import {TYPE_PORTAL} from '../utils/elementType'; +import {TYPE_PORTAL} from '../../external/JSXElementType'; import type {PortalType} from '../Types'; export function createPortal( diff --git a/libs/horizon/src/renderer/components/ForwardRef.ts b/libs/horizon/src/renderer/components/ForwardRef.ts index 5baef940..63b2c791 100644 --- a/libs/horizon/src/renderer/components/ForwardRef.ts +++ b/libs/horizon/src/renderer/components/ForwardRef.ts @@ -1,4 +1,4 @@ -import {TYPE_FORWARD_REF} from '../utils/elementType'; +import {TYPE_FORWARD_REF} from '../../external/JSXElementType'; export function forwardRef(render: Function) { return { diff --git a/libs/horizon/src/renderer/components/Lazy.ts b/libs/horizon/src/renderer/components/Lazy.ts index 4b37091e..8b151da8 100644 --- a/libs/horizon/src/renderer/components/Lazy.ts +++ b/libs/horizon/src/renderer/components/Lazy.ts @@ -1,6 +1,6 @@ import type {PromiseType} from '../Types'; -import {TYPE_LAZY} from '../utils/elementType'; +import {TYPE_LAZY} from '../../external/JSXElementType'; enum LayStatus { UnProcessed = 'UnProcessed', diff --git a/libs/horizon/src/renderer/components/Memo.ts b/libs/horizon/src/renderer/components/Memo.ts index 90d3c140..9d84e8c1 100644 --- a/libs/horizon/src/renderer/components/Memo.ts +++ b/libs/horizon/src/renderer/components/Memo.ts @@ -1,4 +1,4 @@ -import {TYPE_MEMO} from '../utils/elementType'; +import {TYPE_MEMO} from '../../external/JSXElementType'; export function memo(type, compare?: (oldProps: Props, newProps: Props) => boolean) { return { diff --git a/libs/horizon/src/renderer/components/context/CreateContext.ts b/libs/horizon/src/renderer/components/context/CreateContext.ts index 45d97039..cbd6f96f 100644 --- a/libs/horizon/src/renderer/components/context/CreateContext.ts +++ b/libs/horizon/src/renderer/components/context/CreateContext.ts @@ -1,5 +1,5 @@ import type {ContextType} from '../../Types'; -import {TYPE_PROVIDER, TYPE_CONTEXT} from '../../utils/elementType'; +import {TYPE_PROVIDER, TYPE_CONTEXT} from '../../../external/JSXElementType'; export function createContext(val: T): ContextType { const context: ContextType = { diff --git a/libs/horizon/src/renderer/diff/DiffTools.ts b/libs/horizon/src/renderer/diff/DiffTools.ts index 928f889b..0b14f611 100644 --- a/libs/horizon/src/renderer/diff/DiffTools.ts +++ b/libs/horizon/src/renderer/diff/DiffTools.ts @@ -1,27 +1,11 @@ -import type { VNode, HorizonElement } from '../Types'; +import type { VNode, JSXElement } from '../Types'; // 当前vNode和element是同样的类型 // LazyComponent 会修改type的类型,所以特殊处理这种类型 -export const isSameType = (vNode: VNode, ele: HorizonElement) => { +export const isSameType = (vNode: VNode, ele: JSXElement) => { return vNode.type === ele.type || (vNode.isLazyComponent && vNode.lazyType === ele.type); }; -export function createRef(element: HorizonElement) { - const elementRef = element.ref; - // 如果ref是null、function、object,直接返回 - if (elementRef === null || typeof elementRef === 'function' || typeof elementRef === 'object') { - return elementRef; - } else { // 包装成函数 - if (element._vNode) { - let inst = element._vNode.realNode; - - return function(instance) { - inst.refs[String(elementRef)] = instance; - }; - } - } -} - export function isTextType(newChild: any) { return typeof newChild === 'string' || typeof newChild === 'number'; } diff --git a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts index 606a97d9..cf2a7993 100644 --- a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts +++ b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts @@ -1,11 +1,10 @@ import type { VNode } from '../Types'; import { FlagUtils } from '../vnode/VNodeFlags'; -import { TYPE_ELEMENT, TYPE_FRAGMENT, TYPE_PORTAL } from '../utils/elementType'; +import { TYPE_COMMON_ELEMENT, TYPE_FRAGMENT, TYPE_PORTAL } from '../../external/JSXElementType'; import { DomText, DomPortal, Fragment } from '../vnode/VNodeTags'; import {updateVNode, createVNode, createVNodeFromElement, updateVNodePath} from '../vnode/VNodeCreator'; import { isSameType, - createRef, getIteratorFn, isTextType, isArrayType, @@ -60,7 +59,7 @@ function checkCanReuseNode(oldNode: VNode | null, newChild: any): boolean { if (isArrayType(newChild) || isIteratorType(newChild)) { return oldKey === null; } - if (newChild.vtype === TYPE_ELEMENT || newChild.vtype === TYPE_PORTAL) { + if (newChild.vtype === TYPE_COMMON_ELEMENT || newChild.vtype === TYPE_PORTAL) { return oldKey === newChild.key; } } @@ -79,7 +78,7 @@ function getNodeType(newChild: any): string { if (isArrayType(newChild) || isIteratorType(newChild)) { return DiffCategory.ARR_NODE; } - if (newChild.vtype === TYPE_ELEMENT || newChild.vtype === TYPE_PORTAL) { + if (newChild.vtype === TYPE_COMMON_ELEMENT || newChild.vtype === TYPE_PORTAL) { return DiffCategory.OBJECT_NODE; } } @@ -129,7 +128,7 @@ function getNewNode(parentNode: VNode, newChild: any, oldNode: VNode | null) { break; } case DiffCategory.OBJECT_NODE: { - if (newChild.vtype === TYPE_ELEMENT) { + if (newChild.vtype === TYPE_COMMON_ELEMENT) { if (newChild.type === TYPE_FRAGMENT) { if (oldNode === null || oldNode.tag !== Fragment) { const key = oldNode !== null ? oldNode.key : newChild.key; @@ -142,10 +141,12 @@ function getNewNode(parentNode: VNode, newChild: any, oldNode: VNode | null) { if (oldNode === null || !isSameType(oldNode, newChild)) { resultNode = createVNodeFromElement(newChild); - resultNode.ref = createRef(newChild); + resultNode.ref = newChild.ref; + resultNode.belongClassVNode = newChild.belongClassVNode; } else { resultNode = updateVNode(oldNode, newChild.props); - resultNode.ref = createRef(newChild); + resultNode.ref = newChild.ref; + resultNode.belongClassVNode = newChild.belongClassVNode; } break; } else if (newChild.vtype === TYPE_PORTAL) { @@ -200,7 +201,7 @@ function getOldNodeFromMap(nodeMap: Map, newIdx: number, if (isArrayType(newChild) || isIteratorType(newChild)) { return nodeMap.get(newIdx) || null; } - if (newChild.vtype === TYPE_ELEMENT || newChild.vtype === TYPE_PORTAL) { + if (newChild.vtype === TYPE_COMMON_ELEMENT || newChild.vtype === TYPE_PORTAL) { return nodeMap.get(newChild.key === null ? newIdx : newChild.key) || null; } } @@ -539,7 +540,7 @@ function diffObjectNodeHandler( let resultNode: VNode | null = null; let startDelVNode = firstChildVNode; - if (newChild.vtype === TYPE_ELEMENT) { + if (newChild.vtype === TYPE_COMMON_ELEMENT) { if (canReuseNode) { // 可以复用 if (canReuseNode.tag === Fragment && newChild.type === TYPE_FRAGMENT) { @@ -548,7 +549,8 @@ function diffObjectNodeHandler( resultNode.next = null; } else if (isSameType(canReuseNode, newChild)) { resultNode = updateVNode(canReuseNode, newChild.props); - resultNode.ref = createRef(newChild); + resultNode.ref = newChild.ref; + resultNode.belongClassVNode = newChild.belongClassVNode; startDelVNode = getSiblingVNode(resultNode); resultNode.next = null; } @@ -560,7 +562,8 @@ function diffObjectNodeHandler( resultNode = createVNode(Fragment, newChild.key, newChild.props.children); } else { resultNode = createVNodeFromElement(newChild); - resultNode.ref = createRef(newChild); + resultNode.ref = newChild.ref; + resultNode.belongClassVNode = newChild.belongClassVNode; } } } else if (newChild.vtype === TYPE_PORTAL) { diff --git a/libs/horizon/src/renderer/render/ClassComponent.ts b/libs/horizon/src/renderer/render/ClassComponent.ts index 72ff9224..b0942a1b 100644 --- a/libs/horizon/src/renderer/render/ClassComponent.ts +++ b/libs/horizon/src/renderer/render/ClassComponent.ts @@ -27,8 +27,8 @@ import { processUpdates, } from '../UpdateHandler'; import { getContextChangeCtx, setContextChangeCtx } from '../ContextSaver'; -import ProcessingVNode from '../vnode/ProcessingVNode'; -import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator'; +import { setProcessingClassVNode } from '../GlobalVar'; +import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator'; export function captureRender(processing: VNode): VNode | null { const clazz = processing.type; @@ -158,7 +158,8 @@ function mountInstance(clazz, processing: VNode, nextProps: object) { function createChildren(clazz: any, processing: VNode) { markRef(processing); - ProcessingVNode.val = processing; + setProcessingClassVNode(processing); + processing.state = processing.realNode.state; const inst = processing.realNode; diff --git a/libs/horizon/src/renderer/render/MemoComponent.ts b/libs/horizon/src/renderer/render/MemoComponent.ts index 2c24c0d7..fd273249 100644 --- a/libs/horizon/src/renderer/render/MemoComponent.ts +++ b/libs/horizon/src/renderer/render/MemoComponent.ts @@ -7,7 +7,7 @@ import { TYPE_FRAGMENT, TYPE_PROFILER, TYPE_STRICT_MODE, -} from '../utils/elementType'; +} from '../../external/JSXElementType'; import {Fragment} from '../vnode/VNodeTags'; export function captureRender(processing: VNode, shouldUpdate: boolean): VNode | null { diff --git a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts index 779f3653..1651839b 100644 --- a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts +++ b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts @@ -157,13 +157,19 @@ function hideOrUnhideAllChildren(vNode, isHidden) { function attachRef(vNode: VNode) { const ref = vNode.ref; + if (ref !== null) { const instance = vNode.realNode; - if (typeof ref === 'function') { + let refType = typeof ref; + if (refType === 'function') { ref(instance); - } else { + } else if (refType === 'object') { (ref).current = instance; + } else { + if (vNode.belongClassVNode && vNode.belongClassVNode.realNode) { + vNode.belongClassVNode.realNode.refs[String(ref)] = instance; + } } } } @@ -172,14 +178,20 @@ function detachRef(vNode: VNode, isOldRef?: boolean) { let ref = (isOldRef ? vNode.oldRef : vNode.ref); if (ref !== null) { - if (typeof ref === 'function') { + let refType = typeof ref; + + if (refType === 'function') { try { ref(null); } catch (error) { handleSubmitError(vNode, error); } - } else { + } else if (refType === 'object') { (ref).current = null; + } else { + if (vNode.belongClassVNode && vNode.belongClassVNode.realNode) { + vNode.belongClassVNode.realNode.refs[String(ref)] = null; + } } } } diff --git a/libs/horizon/src/renderer/submit/Submit.ts b/libs/horizon/src/renderer/submit/Submit.ts index 95ada780..774c7386 100644 --- a/libs/horizon/src/renderer/submit/Submit.ts +++ b/libs/horizon/src/renderer/submit/Submit.ts @@ -11,7 +11,7 @@ import { callBeforeSubmitLifeCycles, submitDeletion, submitAddition, submitResetTextContent, submitUpdate, detachRef, } from './LifeCycleHandler'; -import {tryRenderRoot, setProcessing, getStartVNode} from '../TreeBuilder'; +import {tryRenderRoot, setProcessing} from '../TreeBuilder'; import { BySync, InRender, @@ -24,6 +24,7 @@ import { isSchedulingEffects, setSchedulingEffects, setHookEffectRoot, } from './HookEffectHandler'; +import {getStartVNode} from '../GlobalVar'; let rootThrowError = null; diff --git a/libs/horizon/src/renderer/vnode/ProcessingVNode.ts b/libs/horizon/src/renderer/vnode/ProcessingVNode.ts deleted file mode 100644 index 1110242c..00000000 --- a/libs/horizon/src/renderer/vnode/ProcessingVNode.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type {VNode} from '../Types'; - -// 当前所有者是应拥有当前正在构建的任何组件的组件。 -const ProcessingVNode: { val: VNode | null } = { - val: null, -}; - -export default ProcessingVNode; diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 8c16fad4..810c7fb5 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -76,6 +76,8 @@ export class VNode { path: Array = []; // 保存从根到本节点的路径 toUpdateNodes: Set | null = null; // 保存要更新的节点 + belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用 + constructor(tag: VNodeTag, props: any, key: null | string, outerDom) { this.tag = tag; // 对应组件的类型,比如ClassComponent等 this.key = key; diff --git a/libs/horizon/src/renderer/vnode/VNodeCreator.ts b/libs/horizon/src/renderer/vnode/VNodeCreator.ts index 71c4a8dc..45648678 100644 --- a/libs/horizon/src/renderer/vnode/VNodeCreator.ts +++ b/libs/horizon/src/renderer/vnode/VNodeCreator.ts @@ -24,9 +24,9 @@ import { TYPE_MEMO, TYPE_PROFILER, TYPE_PROVIDER, TYPE_STRICT_MODE, TYPE_SUSPENSE, -} from '../utils/elementType'; +} from '../../external/JSXElementType'; import { VNode } from './VNode'; -import {HorizonElement} from '../Types'; +import {JSXElement} from '../Types'; const typeLazyMap = { [TYPE_FORWARD_REF]: ForwardRef, @@ -156,7 +156,7 @@ export function updateVNodePath(vNode: VNode) { vNode.path = [...vNode.parent.path, vNode.cIndex]; } -export function createVNodeFromElement(element: HorizonElement): VNode { +export function createVNodeFromElement(element: JSXElement): VNode { const type = element.type; const key = element.key; const props = element.props; diff --git a/libs/horizon/src/renderer/vnode/VNodeUtils.ts b/libs/horizon/src/renderer/vnode/VNodeUtils.ts index 0183dd67..44f1544c 100644 --- a/libs/horizon/src/renderer/vnode/VNodeUtils.ts +++ b/libs/horizon/src/renderer/vnode/VNodeUtils.ts @@ -13,7 +13,7 @@ export function getSiblingVNode(node) { } export function travelChildren(beginVNode: VNode, handleVNode: Function, isFinish?: Function) { - let node = beginVNode; + let node: VNode | null = beginVNode; while (node !== null) { if (isFinish && isFinish(node)) { @@ -77,10 +77,6 @@ export function travelVNodeTree( // 置空vNode export function clearVNode(vNode: VNode) { - clearOneVNode(vNode); -} - -function clearOneVNode(vNode: VNode) { vNode.child = null; vNode.next = null; vNode.depContexts = []; @@ -105,6 +101,8 @@ function clearOneVNode(vNode: VNode) { vNode.path = []; vNode.toUpdateNodes = null; + + vNode.belongClassVNode = null; } // 是dom类型的vNode @@ -224,7 +222,7 @@ function isSameContainer( } // 注释类型的节点 if (isComment(container) && container.parentNode === targetContainer) { - return true + return true; } return false; } From 119cf01c4c78a36e9da04dcdfeba5a17de7da28d Mon Sep 17 00:00:00 2001 From: * <8> Date: Fri, 21 Jan 2022 10:14:47 +0800 Subject: [PATCH 09/11] Match-id-9bffeeea7e66bff2477c913ce3b955ac551fd483 --- libs/horizon/src/renderer/render/ClassComponent.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/horizon/src/renderer/render/ClassComponent.ts b/libs/horizon/src/renderer/render/ClassComponent.ts index 81166c78..b459215c 100644 --- a/libs/horizon/src/renderer/render/ClassComponent.ts +++ b/libs/horizon/src/renderer/render/ClassComponent.ts @@ -69,7 +69,7 @@ function mountInstance(clazz, processing: VNode, nextProps: object) { function createChildren(clazz: any, processing: VNode) { markRef(processing); - ProcessingVNode.val = processing; + setProcessingClassVNode(processing); processing.state = processing.realNode.state; const inst = processing.realNode; From 66ca6c968ab3e6314e5ce7359849c2687cd82fdb Mon Sep 17 00:00:00 2001 From: * <8> Date: Fri, 21 Jan 2022 11:07:16 +0800 Subject: [PATCH 10/11] Match-id-380f75464e0bb10e9f8ea83e11081332842887f4 --- libs/horizon/src/renderer/TreeBuilder.ts | 110 +++++++++--------- libs/horizon/src/renderer/Types.ts | 16 +-- .../src/renderer/components/CreatePortal.ts | 4 +- libs/horizon/src/renderer/components/Lazy.ts | 10 +- .../components/context/CompatibleContext.ts | 2 +- libs/horizon/src/renderer/hooks/HookStage.ts | 2 +- .../src/renderer/render/ContextProvider.ts | 55 ++++----- .../src/renderer/render/DomComponent.ts | 102 ++++++++-------- libs/horizon/src/renderer/render/DomPortal.ts | 8 +- .../src/renderer/render/FunctionComponent.ts | 87 +++++++------- 10 files changed, 199 insertions(+), 197 deletions(-) diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts index 0d3bfa9e..691cb409 100644 --- a/libs/horizon/src/renderer/TreeBuilder.ts +++ b/libs/horizon/src/renderer/TreeBuilder.ts @@ -69,6 +69,16 @@ function resetProcessingVariables(startUpdateVNode: VNode) { unrecoverableErrorDuringBuild = null; } +// 收集有变化的节点,在submit阶段继续处理 +function collectDirtyNodes(vNode: VNode, parent: VNode): void { + // 将子树和此vNode的所有效果附加到父树的效果列表中,子项的完成顺序会影响副作用顺序。 + parent.dirtyNodes.push(...vNode.dirtyNodes); + + if (FlagUtils.hasAnyFlag(vNode)) { + parent.dirtyNodes.push(vNode); + } +} + // ============================== 向上递归 ============================== // 尝试完成当前工作单元,然后移动到下一个兄弟工作单元。如果没有更多的同级,请返回父vNode。 @@ -134,6 +144,51 @@ function handleError(root, error): void { bubbleVNode(processing); } +// 从多个更新节点中,计算出开始节点。即:找到最近的共同的父辈节点 +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 buildVNodeTree(treeRoot: VNode) { const preMode = copyExecuteMode(); @@ -271,67 +326,12 @@ function getChildByIndex(vNode: VNode, idx: number) { 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() { if (getBuildResult() !== BuildCompleted) { setBuildResult(BuildErrored); } } -// 收集有变化的节点,在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)) { diff --git a/libs/horizon/src/renderer/Types.ts b/libs/horizon/src/renderer/Types.ts index 2f3fad4b..e12d35b0 100644 --- a/libs/horizon/src/renderer/Types.ts +++ b/libs/horizon/src/renderer/Types.ts @@ -5,24 +5,24 @@ type Trigger = (A) => void; export type UseStateHookType = { useState( initialState: (() => S) | S - ): [S, Trigger<((S) => S) | S>] + ): [S, Trigger<((S) => S) | S>]; }; export type UseReducerHookType = { useReducer( reducer: (S, A) => S, initArg: P, init?: (P) => S, - ): [S, Trigger] + ): [S, Trigger]; }; export type UseContextHookType = { useContext(context: ContextType,): T }; export type HorizonElement = { - vtype: any, - type: any, - key: any, - ref: any, - props: any, + vtype: any; + type: any; + key: any; + ref: any; + props: any; - _vNode: any, + _vNode: any; }; export type ProviderType = { diff --git a/libs/horizon/src/renderer/components/CreatePortal.ts b/libs/horizon/src/renderer/components/CreatePortal.ts index 3bdb4b17..4b4259e5 100644 --- a/libs/horizon/src/renderer/components/CreatePortal.ts +++ b/libs/horizon/src/renderer/components/CreatePortal.ts @@ -4,11 +4,11 @@ import type {PortalType} from '../Types'; export function createPortal( children: any, outerDom: any, - key: string = null, + key: string = '', ): PortalType { return { vtype: TYPE_PORTAL, - key: key == null ? null : '' + key, + key: key == '' ? '' : '' + key, children, outerDom, }; diff --git a/libs/horizon/src/renderer/components/Lazy.ts b/libs/horizon/src/renderer/components/Lazy.ts index 4b37091e..0252eab3 100644 --- a/libs/horizon/src/renderer/components/Lazy.ts +++ b/libs/horizon/src/renderer/components/Lazy.ts @@ -10,14 +10,14 @@ enum LayStatus { } type LazyContent = { - _status: string, - _value: () => PromiseType<{default: T}> | PromiseType | T | any + _status: string; + _value: () => PromiseType<{default: T}> | PromiseType | T | any; }; export type LazyComponent = { - vtype: number, - _content: P, - _load: (content: P) => T, + vtype: number; + _content: P; + _load: (content: P) => T; }; // lazyContent随着阶段改变,_value改变: diff --git a/libs/horizon/src/renderer/components/context/CompatibleContext.ts b/libs/horizon/src/renderer/components/context/CompatibleContext.ts index 0df1f5a2..0d6a4bd8 100644 --- a/libs/horizon/src/renderer/components/context/CompatibleContext.ts +++ b/libs/horizon/src/renderer/components/context/CompatibleContext.ts @@ -51,7 +51,7 @@ export function getOldContext(processing: VNode, clazz: Function, ifProvider: bo // 当组件既是提供者,也是消费者时,取上一个context,不能直接取最新context,因为已经被更新为当前组件的context; // 当组件只是消费者时,则取最新context - const parentContext = ((ifProvider && isOldProvider(clazz))) ? + const parentContext = (ifProvider && isOldProvider(clazz)) ? getOldPreviousContextCtx() : getOldContextCtx(); diff --git a/libs/horizon/src/renderer/hooks/HookStage.ts b/libs/horizon/src/renderer/hooks/HookStage.ts index 65a96424..c3e9622a 100644 --- a/libs/horizon/src/renderer/hooks/HookStage.ts +++ b/libs/horizon/src/renderer/hooks/HookStage.ts @@ -4,7 +4,7 @@ export enum HookStage { Update = 2, } -let hookStage: HookStage = null; +let hookStage: HookStage | null = null; export function getHookStage() { return hookStage; diff --git a/libs/horizon/src/renderer/render/ContextProvider.ts b/libs/horizon/src/renderer/render/ContextProvider.ts index b980273c..c31a9ff4 100644 --- a/libs/horizon/src/renderer/render/ContextProvider.ts +++ b/libs/horizon/src/renderer/render/ContextProvider.ts @@ -14,6 +14,30 @@ import {launchUpdateFromVNode} from '../TreeBuilder'; import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator'; import {setParentsChildShouldUpdate} from '../vnode/VNodeShouldUpdate'; +// 从依赖中找到匹配context的VNode +function matchDependencies(depContexts, context, vNode): boolean { + for (let i = 0; i < depContexts.length; i++) { + const contextItem = depContexts[i]; + if (contextItem === context) { + // 匹配到了更新的context,需要创建update。 + if (vNode.tag === ClassComponent) { + pushForceUpdate(vNode); + } + + vNode.shouldUpdate = true; + + // 找到需要更新的节点,所以祖先节点都需要改为shouldUpdate为true + setParentsChildShouldUpdate(vNode.parent); + + vNode.isDepContextChange = true; + // 由于我们已经找到匹配项,我们可以停止遍历依赖项列表。 + return true; + } + } + + return false; +} + // 从当前子节点开始向下遍历,找到消费此context的组件,并更新 function handleContextChange(processing: VNode, context: ContextType): void { const vNode = processing.child; @@ -24,15 +48,15 @@ function handleContextChange(processing: VNode, context: ContextType): void let isMatch = false; // 从vNode开始遍历 - travelVNodeTree(vNode, (node) => { + travelVNodeTree(vNode, node => { const depContexts = node.depContexts; if (depContexts.length) { isMatch = matchDependencies(depContexts, context, node) ?? isMatch; } - }, (node) => { + }, node => // 如果这是匹配的provider,则不要更深入地扫描 - return node.tag === ContextProvider && node.type === processing.type; - }, processing); + node.tag === ContextProvider && node.type === processing.type + , processing); // 找到了依赖context的子节点,触发一次更新 if (isMatch) { @@ -80,26 +104,3 @@ export function bubbleRender(processing: VNode) { resetContextCtx(processing); } -// 从依赖中找到匹配context的VNode -function matchDependencies(depContexts, context, vNode): boolean { - for (let i = 0; i < depContexts.length; i++) { - const contextItem = depContexts[i]; - if (contextItem === context) { - // 匹配到了更新的context,需要创建update。 - if (vNode.tag === ClassComponent) { - pushForceUpdate(vNode); - } - - vNode.shouldUpdate = true; - - // 找到需要更新的节点,所以祖先节点都需要改为shouldUpdate为true - setParentsChildShouldUpdate(vNode.parent); - - vNode.isDepContextChange = true; - // 由于我们已经找到匹配项,我们可以停止遍历依赖项列表。 - return true; - } - } - - return false; -} diff --git a/libs/horizon/src/renderer/render/DomComponent.ts b/libs/horizon/src/renderer/render/DomComponent.ts index 88122643..bac845b1 100644 --- a/libs/horizon/src/renderer/render/DomComponent.ts +++ b/libs/horizon/src/renderer/render/DomComponent.ts @@ -17,8 +17,55 @@ import {createVNodeChildren, markRef} from './BaseComponent'; import {DomComponent, DomPortal, DomText} from '../vnode/VNodeTags'; import {getFirstChild, travelVNodeTree} from '../vnode/VNodeUtils'; -export function captureRender(processing: VNode): VNode | null { - return captureDomComponent(processing); +function updateDom( + processing: VNode, + type: any, + newProps: Props, +) { + // 如果oldProps !== newProps,意味着存在更新,并且需要处理其相关的副作用 + const oldProps = processing.oldProps; + if (oldProps === newProps) { + // 如果props没有发生变化,即使它的children发生了变化,我们也不会改变它 + return; + } + + const dom: Element = processing.realNode; + + const changeList = getPropChangeList( + dom, + type, + oldProps, + newProps, + ); + processing.changeList = changeList; + + // 输入类型的直接标记更新 + if (type === 'input' || type === 'textarea' || type === 'select' || type === 'option') { + FlagUtils.markUpdate(processing); + } else { + // 其它的类型,数据有变化才标记更新 + if (changeList.length) { + FlagUtils.markUpdate(processing); + } + } +} + +// 把dom类型的子节点append到parent dom中 +function appendAllChildren(parent: Element, processing: VNode) { + const vNode = processing.child; + if (vNode === null) { + return; + } + + // 向下递归它的子节点,查找所有终端节点。 + travelVNodeTree(vNode, node => { + if (node.tag === DomComponent || node.tag === DomText) { + appendChildElement(parent, node.realNode); + } + }, node => + // 已经append到父节点,或者是DomPortal都不需要处理child了 + node.tag === DomComponent || node.tag === DomText || node.tag === DomPortal + , processing); } export function bubbleRender(processing: VNode) { @@ -86,53 +133,6 @@ function captureDomComponent(processing: VNode): VNode | null { return processing.child; } -// 把dom类型的子节点append到parent dom中 -function appendAllChildren(parent: Element, processing: VNode) { - const vNode = processing.child; - if (vNode === null) { - return; - } - - // 向下递归它的子节点,查找所有终端节点。 - travelVNodeTree(vNode, (node) => { - if (node.tag === DomComponent || node.tag === DomText) { - appendChildElement(parent, node.realNode); - } - }, (node) => { - // 已经append到父节点,或者是DomPortal都不需要处理child了 - return node.tag === DomComponent || node.tag === DomText || node.tag === DomPortal; - }, processing); -} - -function updateDom( - processing: VNode, - type: any, - newProps: Props, -) { - // 如果oldProps !== newProps,意味着存在更新,并且需要处理其相关的副作用 - const oldProps = processing.oldProps; - if (oldProps === newProps) { - // 如果props没有发生变化,即使它的children发生了变化,我们也不会改变它 - return; - } - - const dom: Element = processing.realNode; - - const changeList = getPropChangeList( - dom, - type, - oldProps, - newProps, - ); - processing.changeList = changeList; - - // 输入类型的直接标记更新 - if (type === 'input' || type === 'textarea' || type === 'select' || type === 'option') { - FlagUtils.markUpdate(processing); - } else { - // 其它的类型,数据有变化才标记更新 - if (changeList.length) { - FlagUtils.markUpdate(processing); - } - } +export function captureRender(processing: VNode): VNode | null { + return captureDomComponent(processing); } diff --git a/libs/horizon/src/renderer/render/DomPortal.ts b/libs/horizon/src/renderer/render/DomPortal.ts index 90f0545c..e8da4a6e 100644 --- a/libs/horizon/src/renderer/render/DomPortal.ts +++ b/libs/horizon/src/renderer/render/DomPortal.ts @@ -4,10 +4,6 @@ import { createChildrenByDiff } from '../diff/nodeDiffComparator'; import { createVNodeChildren } from './BaseComponent'; import { prePortal } from '../../dom/DOMOperator'; -export function captureRender(processing: VNode): VNode | null { - return capturePortalComponent(processing); -} - export function bubbleRender(processing: VNode) { resetNamespaceCtx(processing); @@ -27,3 +23,7 @@ function capturePortalComponent(processing: VNode) { } return processing.child; } + +export function captureRender(processing: VNode): VNode | null { + return capturePortalComponent(processing); +} diff --git a/libs/horizon/src/renderer/render/FunctionComponent.ts b/libs/horizon/src/renderer/render/FunctionComponent.ts index 4956d57b..ea975abd 100644 --- a/libs/horizon/src/renderer/render/FunctionComponent.ts +++ b/libs/horizon/src/renderer/render/FunctionComponent.ts @@ -13,23 +13,40 @@ import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator'; // 在useState, useReducer的时候,会触发state变化 let stateChange = false; -export function captureRender(processing: VNode, shouldUpdate?: boolean): VNode | null { - const Component = processing.type; - const unresolvedProps = processing.props; - const resolvedProps = - processing.isLazyComponent - ? mergeDefaultProps(Component, unresolvedProps) - : unresolvedProps; +export function bubbleRender() {} - return captureFunctionComponent( - processing, - Component, - resolvedProps, - shouldUpdate - ); +// 判断children是否可以复用 +function checkIfCanReuseChildren(processing: VNode, shouldUpdate?: boolean) { + let isCanReuse = true; + + if (!processing.isCreated) { + const oldProps = processing.oldProps; + const newProps = processing.props; + + // 如果props或者context改变了 + if (oldProps !== newProps || getContextChangeCtx() || processing.isDepContextChange) { + isCanReuse = false; + } else { + if (shouldUpdate && processing.suspenseChildThrow) { + // 使用完后恢复 + processing.suspenseChildThrow = false; + isCanReuse = false; + } + } + } else { + isCanReuse = false; + } + + return isCanReuse; } -export function bubbleRender() {} +export function setStateChange(isUpdate) { + stateChange = isUpdate; +} + +export function isStateChange() { + return stateChange; +} export function captureFunctionComponent( processing: VNode, @@ -66,35 +83,19 @@ export function captureFunctionComponent( return processing.child; } -// 判断children是否可以复用 -function checkIfCanReuseChildren(processing: VNode, shouldUpdate?: boolean) { - let isCanReuse = true; +export function captureRender(processing: VNode, shouldUpdate?: boolean): VNode | null { + const Component = processing.type; + const unresolvedProps = processing.props; + const resolvedProps = + processing.isLazyComponent + ? mergeDefaultProps(Component, unresolvedProps) + : unresolvedProps; - if (!processing.isCreated) { - const oldProps = processing.oldProps; - const newProps = processing.props; - - // 如果props或者context改变了 - if (oldProps !== newProps || getContextChangeCtx() || processing.isDepContextChange) { - isCanReuse = false; - } else { - if (shouldUpdate && processing.suspenseChildThrow) { - // 使用完后恢复 - processing.suspenseChildThrow = false; - isCanReuse = false; - } - } - } else { - isCanReuse = false; - } - - return isCanReuse; + return captureFunctionComponent( + processing, + Component, + resolvedProps, + shouldUpdate + ); } -export function setStateChange(isUpdate) { - stateChange = isUpdate; -} - -export function isStateChange() { - return stateChange; -} From 7d5fffc2321e7f1793950781f25be1ab123523c0 Mon Sep 17 00:00:00 2001 From: * <8> Date: Fri, 21 Jan 2022 11:22:21 +0800 Subject: [PATCH 11/11] Match-id-4bf53a1a96a0b53ef5e7c1f805eebe2f253d6d38 --- libs/horizon/src/renderer/Types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/horizon/src/renderer/Types.ts b/libs/horizon/src/renderer/Types.ts index 5b6205ca..6765fe10 100644 --- a/libs/horizon/src/renderer/Types.ts +++ b/libs/horizon/src/renderer/Types.ts @@ -21,7 +21,7 @@ export type JSXElement = { key: any; ref: any; props: any; - _vNode: any; + belongClassVNode: any; };