diff --git a/libs/horizon/src/dom/DOMInternalKeys.ts b/libs/horizon/src/dom/DOMInternalKeys.ts index 068eb829..d7d78573 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/dom/SelectionRangeHandler.ts b/libs/horizon/src/dom/SelectionRangeHandler.ts index d4f98942..f5bde5bf 100644 --- a/libs/horizon/src/dom/SelectionRangeHandler.ts +++ b/libs/horizon/src/dom/SelectionRangeHandler.ts @@ -73,6 +73,7 @@ function isInDocument(dom) { if (dom && dom.ownerDocument) { return isNodeContainsByTargetNode(dom.ownerDocument.documentElement, dom); } + return false; } // 判断一个标签是否有设置选择范围的能力 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..c5eff12e 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 { @@ -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. ' - ); - default: - return; - } -} - function processArrayChildren( children: any, arr: Array, @@ -88,11 +49,11 @@ function callMapFun( ? '.$' + mappedChild.key : ''); // 返回一个修改key的children - mappedChild = HorizonElement( + mappedChild = JSXElement( mappedChild.type, newKey, mappedChild.ref, - mappedChild._vNode, + mappedChild.belongClassVNode, mappedChild.props, ); } @@ -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_COMMON_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/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..f0218ac9 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,50 +20,15 @@ export function HorizonElement(type, key, ref, vNode, props) { ref: ref, props: props, - // 记录负责创建此元素的组件。 - _vNode: vNode, + // 所属的class组件 + belongClassVNode: vNode, }; -}; +} function isValidKey(key) { return key !== 'key' && key !== 'ref' && key !== '__source'; } -function buildElement(isClone, type, setting, ...children) { - // setting中的值优先级最高,clone情况下从 type 中取值,创建情况下直接赋值为 null - 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; - - if (setting != null) { - Object.keys(setting).forEach(k => { - if (isValidKey(k)) { - props[k] = setting[k]; - } - }); - if (setting.ref !== undefined && isClone) { - vNode = ProcessingVNode.val; - } - } - - if (children.length) { - 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); -} - -//创建Element结构体,供JSX编译时调用 -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) { @@ -72,11 +37,46 @@ function mergeDefault(sourceObj, defaultObj) { }); } +function buildElement(isClone, type, setting, ...children) { + // setting中的值优先级最高,clone情况下从 type 中取值,创建情况下直接赋值为 null + 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.belongClassVNode : getProcessingClassVNode(); + + if (setting != null) { + Object.keys(setting).forEach(k => { + if (isValidKey(k)) { + props[k] = setting[k]; + } + }); + if (setting.ref !== undefined && isClone) { + vNode = getProcessingClassVNode(); + } + } + + if (children.length) { + props.children = children.length === 1 ? children[0] : children; + } + const element = isClone ? type.type : type; + // 合并默认属性 + if (element && element.defaultProps) { + mergeDefault(props, element.defaultProps); + } + + return JSXElement(element, key, ref, vNode, props); +} + +// 创建Element结构体,供JSX编译时调用 +export function createElement(type, setting, ...children) { + return buildElement(false, type, setting, ...children); +} + export function cloneElement(element, setting, ...children) { return buildElement(true, 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/Renderer.ts b/libs/horizon/src/renderer/Renderer.ts index 60ac72da..d5cf89da 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; @@ -37,11 +37,10 @@ export function startUpdate( launchUpdateFromVNode(treeRoot); } -export function getFirstCustomDom(treeRoot: VNode): Element | Text | null { - if (!treeRoot.child) { - return null; +export function getFirstCustomDom(treeRoot?: VNode | null): Element | Text | 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..a84b5c3d 100644 --- a/libs/horizon/src/renderer/TreeBuilder.ts +++ b/libs/horizon/src/renderer/TreeBuilder.ts @@ -1,16 +1,24 @@ -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 { + BuildCompleted, BuildErrored, + BuildFatalErrored, + BuildInComplete, getBuildResult, + getStartVNode, + setBuildResult, + setProcessingClassVNode, + setStartVNode +} from './GlobalVar'; +import { findDomParent, getSiblingVNode } from './vnode/VNodeUtils'; import { ByAsync, BySync, @@ -21,172 +29,23 @@ import { isExecuting, setExecuteMode } from './ExecuteMode'; -import {recoverParentsContextCtx, resetNamespaceCtx, setNamespaceCtx} from './ContextSaver'; +import { recoverParentsContextCtx, resetNamespaceCtx, setNamespaceCtx } from './ContextSaver'; import { updateChildShouldUpdate, updateParentsChildShouldUpdate, updateShouldUpdateOfTree } from './vnode/VNodeShouldUpdate'; -type BuildVNodeResult = 0 | 1 | 2 | 3; -const BuildInComplete = 0; -const BuildFatalErrored = 1; -const BuildErrored = 2; -const BuildCompleted = 3; - // 当前运行的vNode节点 let processing: VNode | null = null; -// 根节点退出build tree时的状态,如: completed, incomplete, errored, fatalErrored. -let buildVNodeResult: BuildVNodeResult = BuildInComplete; + // 不可恢复错误 let unrecoverableErrorDuringBuild: any = null; -function setBuildResult(result: BuildVNodeResult) { - buildVNodeResult = result; -} - -function getBuildResult(): BuildVNodeResult { - return buildVNodeResult; -} - export function setProcessing(vNode: VNode | null) { processing = vNode; } -let startVNode: VNode | null = null; -export function getStartVNode(): VNode | null { - return startVNode; -} -export function setStartVNode(vNode: VNode | null) { - startVNode = vNode; -} - -// 发起更新 -export function launchUpdateFromVNode(vNode: VNode) { - // 检查循环调用 - checkLoopingUpdateLimit(); - - // 从当前vNode向上遍历到根节点,修改vNode.shouldUpdate和parent.childShouldUpdate - const treeRoot = updateShouldUpdateOfTree(vNode); - if (treeRoot === null) { - // 可能场景是:the componentWillUnmount method 或 useEffect cleanup function 方法中写异步任务,并且修改state。 - // 因为异步回调的时候root都可能被清除了。 - return null; - } - - // 保存待刷新的节点 - treeRoot.toUpdateNodes.add(vNode); - - if (checkMode(BySync) && // 非批量 - !checkMode(InRender)) { // 不是渲染阶段触发 - - // 业务直接调用Horizon.render的时候会进入这个分支,同步渲染。 - // 不能改成下面的异步,否则会有时序问题,因为业务可能会依赖这个渲染的完成。 - renderFromRoot(treeRoot); - } else { - tryRenderRoot(treeRoot); - - if (!isExecuting()) { - // 同步执行 - callRenderQueueImmediate(); - } - } -} - -// 从多个更新节点中,计算出开始节点。即:找到最近的共同的父辈节点 -export function calcStartUpdateVNode(treeRoot: VNode) { - const toUpdateNodes = Array.from(treeRoot.toUpdateNodes); - - if (toUpdateNodes.length === 0) { - return treeRoot; - } - - if (toUpdateNodes.length === 1) { - return toUpdateNodes[0]; - } - - // 要计算的节点过多,直接返回根节点 - if (toUpdateNodes.length > 100) { - return treeRoot; - } - - // 找到路径最短的长度 - let minPath = toUpdateNodes[0].path.length; - for (let i = 1; i < toUpdateNodes.length; i++) { - let pathLen = toUpdateNodes[i].path.length; - if (pathLen < minPath) { - minPath = pathLen; - } - } - - // 找出开始不相等的idx - let idx = 0; - for (; idx < minPath; idx++) { - if (!isEqualByIndex(idx, toUpdateNodes)) { - break; - } - } - // 得到相等的路径 - let startNodePath = toUpdateNodes[0].path.slice(0, idx); - - let node = treeRoot; - for (let i = 1; i < startNodePath.length; i++) { - let pathIndex = startNodePath[i]; - node = 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,81 +54,13 @@ function resetProcessingVariables(startUpdateVNode: VNode) { unrecoverableErrorDuringBuild = null; } -// ============================== 深度遍历 ============================== -function buildVNodeTree(treeRoot: VNode) { - const preMode = copyExecuteMode(); - changeMode(InRender, true); +// 收集有变化的节点,在submit阶段继续处理 +function collectDirtyNodes(vNode: VNode, parent: VNode): void { + // 将子树和此vNode的所有效果附加到父树的效果列表中,子项的完成顺序会影响副作用顺序。 + parent.dirtyNodes.push(...vNode.dirtyNodes); - // 计算出开始节点 - const startUpdateVNode = calcStartUpdateVNode(treeRoot); - - // 缓存起来 - setStartVNode(startUpdateVNode); - - // 清空toUpdateNodes - treeRoot.toUpdateNodes.clear(); - - if (startUpdateVNode.tag !== TreeRoot) { // 不是根节点 - // 设置namespace,用于createElement - const parentObj = findDomParent(startUpdateVNode); - - // 当在componentWillUnmount中调用setState,parent可能是null,因为startUpdateVNode会被clear - if (parentObj !== null) { - const domParent = parentObj.parent; - resetNamespaceCtx(domParent); - setNamespaceCtx(domParent, domParent.outerDom); - } - - // 恢复父节点的context - recoverParentsContextCtx(startUpdateVNode); - } - - // 重置环境变量 - resetProcessingVariables(startUpdateVNode); - - do { - try { - while (processing !== null) { - // 捕获创建 vNodes - const next = captureVNode(processing); - - if (next === null) { - // 如果没有产生新的,那么就完成当前节点,向上遍历 - bubbleVNode(processing); - } else { - processing = next; - } - - ProcessingVNode.val = null; - } - break; - } catch (thrownValue) { - handleError(treeRoot, thrownValue); - } - } while (true); - - setExecuteMode(preMode); -} - -function handleError(root, error): void { - if (processing === null || processing.parent === null) { - // 这是一个致命的错误,因为没有祖先可以处理它 - setBuildResult(BuildFatalErrored); - unrecoverableErrorDuringBuild = error; - - processing = null; - return; - } - - // 处理capture和bubble阶段抛出的错误 - handleRenderThrowError(processing, error); - - bubbleVNode(processing); -} - -export function setBuildResultError() { - if (getBuildResult() !== BuildCompleted) { - setBuildResult(BuildErrored); + if (FlagUtils.hasAnyFlag(vNode)) { + parent.dirtyNodes.push(vNode); } } @@ -322,13 +113,207 @@ function bubbleVNode(vNode: VNode): void { } } -// 收集有变化的节点,在submit阶段继续处理 -function collectDirtyNodes(vNode: VNode, parent: VNode): void { - // 将子树和此vNode的所有效果附加到父树的效果列表中,子项的完成顺序会影响副作用顺序。 - parent.dirtyNodes.push(...vNode.dirtyNodes); +function handleError(root, error): void { + if (processing === null || processing.parent === null) { + // 这是一个致命的错误,因为没有祖先可以处理它 + setBuildResult(BuildFatalErrored); + unrecoverableErrorDuringBuild = error; - if (FlagUtils.hasAnyFlag(vNode)) { - parent.dirtyNodes.push(vNode); + processing = null; + return; + } + + // 处理capture和bubble阶段抛出的错误 + handleRenderThrowError(processing, error); + + 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(); + changeMode(InRender, true); + + // 计算出开始节点 + const startUpdateVNode = calcStartUpdateVNode(treeRoot); + // 缓存起来 + setStartVNode(startUpdateVNode); + + // 清空toUpdateNodes + treeRoot.toUpdateNodes.clear(); + + if (startUpdateVNode.tag !== TreeRoot) { // 不是根节点 + // 设置namespace,用于createElement + const parentObj = findDomParent(startUpdateVNode); + + // 当在componentWillUnmount中调用setState,parent可能是null,因为startUpdateVNode会被clear + if (parentObj !== null) { + const domParent = parentObj.parent; + resetNamespaceCtx(domParent); + setNamespaceCtx(domParent, domParent.outerDom); + } + + // 恢复父节点的context + recoverParentsContextCtx(startUpdateVNode); + } + + // 重置环境变量,为重新进行深度遍历做准备 + resetProcessingVariables(startUpdateVNode); + + do { + try { + while (processing !== null) { + // 捕获创建 vNodes + const next = captureVNode(processing); + + if (next === null) { + // 如果没有产生新的,那么就完成当前节点,向上遍历 + bubbleVNode(processing); + } else { + processing = next; + } + } + + setProcessingClassVNode(null); + + break; + } catch (thrownValue) { + handleError(treeRoot, thrownValue); + } + } while (true); + + setExecuteMode(preMode); +} + +// 总体任务入口 +function renderFromRoot(treeRoot) { + runAsyncEffects(); + + // 1. 构建vNode树 + buildVNodeTree(treeRoot); + + // 致命错误直接抛出 + if (getBuildResult() === BuildFatalErrored) { + throw unrecoverableErrorDuringBuild; + } + + // 2. 提交变更 + submitToRender(treeRoot); + + 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) { + // 检查循环调用 + checkLoopingUpdateLimit(); + + // 从当前vNode向上遍历到根节点,修改vNode.shouldUpdate和parent.childShouldUpdate + const treeRoot = updateShouldUpdateOfTree(vNode); + if (treeRoot === null) { + // 可能场景是:the componentWillUnmount method 或 useEffect cleanup function 方法中写异步任务,并且修改state。 + // 因为异步回调的时候root都可能被清除了。 + return; + } + + // 保存待刷新的节点 + 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 setBuildResultError() { + if (getBuildResult() !== BuildCompleted) { + setBuildResult(BuildErrored); } } diff --git a/libs/horizon/src/renderer/Types.ts b/libs/horizon/src/renderer/Types.ts index b1732fd1..6765fe10 100644 --- a/libs/horizon/src/renderer/Types.ts +++ b/libs/horizon/src/renderer/Types.ts @@ -2,28 +2,27 @@ export { VNode } from './vnode/VNode'; type Trigger = (A) => void; -export type ReadContextHookType = { readContext(context: ContextType): T }; 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, - - _vNode: any, +export type JSXElement = { + vtype: any; + type: any; + key: any; + ref: any; + props: any; + + belongClassVNode: any; }; export type ProviderType = { diff --git a/libs/horizon/src/renderer/UpdateHandler.ts b/libs/horizon/src/renderer/UpdateHandler.ts index 5e54e365..d00f3ce9 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; @@ -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 @@ -50,24 +47,25 @@ 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} + case UpdateState.Update: + 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; } - return oldState; } // 收集callback diff --git a/libs/horizon/src/renderer/components/CreatePortal.ts b/libs/horizon/src/renderer/components/CreatePortal.ts index 3bdb4b17..38d30ca8 100644 --- a/libs/horizon/src/renderer/components/CreatePortal.ts +++ b/libs/horizon/src/renderer/components/CreatePortal.ts @@ -1,14 +1,14 @@ -import {TYPE_PORTAL} from '../utils/elementType'; +import {TYPE_PORTAL} from '../../external/JSXElementType'; 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/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..ecdec465 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', @@ -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/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/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/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 1adf32c8..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; } } @@ -354,9 +355,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 +368,7 @@ function diffArrayNodes( // 从Map删除,后面不会deleteVNode leftChildrenMap.delete(newNode.key || leftIdx); } + if (oldNodeFromMap !== null) { let eIndex = newNode.eIndex; eIndexes.push(eIndex); @@ -380,7 +382,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 +405,7 @@ function diffArrayNodes( appendNode(newNode); } } + if (isComparing) { // 向前回溯找到正确的结果 let length = result.length; @@ -411,9 +414,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 +493,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 +523,7 @@ function diffObjectNodeHandler( firstChildVNode: VNode, isComparing: boolean ) { - let canReuseNode = null; + let canReuseNode: VNode | null = null; // 通过key比对是否有可以reuse const newKey = newChild.key; @@ -535,9 +538,9 @@ function diffObjectNodeHandler( } } - let resultNode = null; + 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) { @@ -546,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; } @@ -558,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/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/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/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..b459215c 100644 --- a/libs/horizon/src/renderer/render/ClassComponent.ts +++ b/libs/horizon/src/renderer/render/ClassComponent.ts @@ -20,33 +20,88 @@ import { markComponentDidUpdate, markGetSnapshotBeforeUpdate, } from './class/ClassLifeCycleProcessor'; -import {FlagUtils} from '../vnode/VNodeFlags'; +import { FlagUtils } from '../vnode/VNodeFlags'; import { createVNodeChildren, markRef } from './BaseComponent'; import { createUpdateArray, 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; - 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); + + setProcessingClassVNode(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..c31a9ff4 100644 --- a/libs/horizon/src/renderer/render/ContextProvider.ts +++ b/libs/horizon/src/renderer/render/ContextProvider.ts @@ -14,46 +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; - - const newProps = processing.props; - const oldProps = !processing.isCreated ? processing.oldProps : null; - - // 获取provider设置的context,即provider组件设置的value - const newCtx = newProps.value; - - // 更新processing的context值为newProps.value - setContextCtx(processing, newCtx); - - if (oldProps !== null) { - const oldCtx = oldProps.value; - const isSameContext = isSame(oldCtx, newCtx); - if (isSameContext) { - // context没有改变,复用 - if (oldProps.children === newProps.children && !getContextChangeCtx()) { - return onlyUpdateChildVNodes(processing); - } - } else { - // context更改,更新所有依赖的组件 - handleContextChange(processing, contextType); - } - } - - const newElements = newProps.children; - processing.child = createVNodeChildren(processing, newElements); - return processing.child; -} - // 从依赖中找到匹配context的VNode function matchDependencies(depContexts, context, vNode): boolean { for (let i = 0; i < depContexts.length; i++) { @@ -88,18 +48,59 @@ 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) { launchUpdateFromVNode(processing); } } + +function captureContextProvider(processing: VNode): VNode | null { + const providerType: ProviderType = processing.type; + const contextType: ContextType = providerType._context; + + const newProps = processing.props; + const oldProps = !processing.isCreated ? processing.oldProps : null; + + // 获取provider设置的context,即provider组件设置的value + const newCtx = newProps.value; + + // 更新processing的context值为newProps.value + setContextCtx(processing, newCtx); + + if (oldProps !== null) { + const oldCtx = oldProps.value; + const isSameContext = isSame(oldCtx, newCtx); + if (isSameContext) { + // context没有改变,复用 + if (oldProps.children === newProps.children && !getContextChangeCtx()) { + return onlyUpdateChildVNodes(processing); + } + } else { + // context更改,更新所有依赖的组件 + handleContextChange(processing, contextType); + } + } + + const newElements = newProps.children; + processing.child = createVNodeChildren(processing, newElements); + return processing.child; +} + +export function captureRender(processing: VNode): VNode | null { + return captureContextProvider(processing); +} + +export function bubbleRender(processing: VNode) { + resetContextCtx(processing); +} + 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; -} diff --git a/libs/horizon/src/renderer/render/LazyComponent.ts b/libs/horizon/src/renderer/render/LazyComponent.ts index 65f7285c..f3dcc954 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, @@ -64,12 +64,13 @@ function captureLazyComponent( Component, '', ); + return null; } } 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/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 b23beb93..46260ac0 100644 --- a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts +++ b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts @@ -70,8 +70,9 @@ function callBeforeSubmitLifeCycles( case TreeRoot: { const root = vNode.realNode; clearContainer(root.outerDom); - return; } + + // No Default } } @@ -136,8 +137,9 @@ function callAfterSubmitLifeCycles( vNode.realNode.focus(); } } - return; } + + // No Default } } @@ -157,13 +159,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,25 +180,26 @@ 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; + } } } } // 卸载一个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/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 997d8bca..810c7fb5 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; // 是否使用强制更新 @@ -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/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; diff --git a/libs/horizon/src/renderer/vnode/VNodeUtils.ts b/libs/horizon/src/renderer/vnode/VNodeUtils.ts index 0183dd67..09ff6f0e 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 @@ -144,6 +142,7 @@ export function findDomVNode(vNode: VNode): VNode | null { if (node.tag === DomComponent || node.tag === DomText) { return node; } + return null; }); } @@ -224,7 +223,7 @@ function isSameContainer( } // 注释类型的节点 if (isComment(container) && container.parentNode === targetContainer) { - return true + return true; } return false; }