diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts index ad709044..7d6201a2 100644 --- a/libs/horizon/src/renderer/TreeBuilder.ts +++ b/libs/horizon/src/renderer/TreeBuilder.ts @@ -54,6 +54,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。 @@ -119,6 +129,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(); @@ -258,67 +313,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 34640d51..6765fe10 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 JSXElement = { - vtype: any, - type: any, - key: any, - ref: any, - props: any, - - belongClassVNode: any, + vtype: any; + type: any; + key: any; + ref: any; + props: 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 2b018f16..38d30ca8 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 8b151da8..ecdec465 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; -}