diff --git a/libs/horizon/src/renderer/ContextSaver.ts b/libs/horizon/src/renderer/ContextSaver.ts new file mode 100644 index 00000000..b5200eaa --- /dev/null +++ b/libs/horizon/src/renderer/ContextSaver.ts @@ -0,0 +1,147 @@ +/** + * 保存与深度遍历相关的一些context。 + * 在深度遍历过程中,begin阶段会修改一些全局的值,在complete阶段会恢复。 + */ + +import type {VNode, ContextType} from './Types'; +import type {Container} from '../dom/DOMOperator'; + +import {getNSCtx} from '../dom/DOMOperator'; +import {ContextProvider} from './vnode/VNodeTags'; + +// 保存的是“http://www.w3.org/1999/xhtml”或“http://www.w3.org/2000/svg”, +// 用于识别是使用document.createElement()还是使用document.createElementNS()创建DOM +const CTX_NAMESPACE = 'CTX_NAMESPACE'; + +// 保存的是Horizon.createContext()的值,或Provider重新设置的值 +const CTX_CONTEXT = 'CTX_CONTEXT'; + +// 旧版context API,是否更改。 +const CTX_OLD_CHANGE = 'CTX_OLD_CHANGE'; +// 旧版context API,保存的是的当前组件提供给子组件使用的context。 +const CTX_OLD_CONTEXT = 'CTX_OLD_CONTEXT'; +// 旧版context API,保存的是的上一个提供者提供给后代组件使用的context。 +const CTX_OLD_PREVIOUS_CONTEXT = 'CTX_OLD_PREVIOUS_CONTEXT'; + +let ctxNamespace: string = ''; + +let ctxOldContext: Object = {}; +let ctxOldChange: Boolean = false; +let ctxOldPreviousContext: Object = {}; + +// capture阶段设置 +function setNamespaceCtx(vNode: VNode, dom?: Container) { + const nextContext = getNSCtx(dom, ctxNamespace, vNode.type); + + vNode.setContext(CTX_NAMESPACE, ctxNamespace); + ctxNamespace = nextContext; +} + +// bubble阶段恢复 +function resetNamespaceCtx(vNode: VNode) { + ctxNamespace = vNode.getContext(CTX_NAMESPACE); +} + +function getNamespaceCtx(): string { + return ctxNamespace; +} + +function setContextCtx(providerVNode: VNode, nextValue: T) { + const context: ContextType = providerVNode.type._context; + + providerVNode.setContext(CTX_CONTEXT, context.value); + context.value = nextValue; +} + +function resetContextCtx(providerVNode: VNode) { + const context: ContextType = providerVNode.type._context; + + context.value = providerVNode.getContext(CTX_CONTEXT); +} + +// 在局部更新时,恢复父节点的context +function recoverParentsContextCtx(vNode: VNode) { + let parent = vNode.parent; + + while (parent !== null) { + if (parent.tag === ContextProvider) { + const newValue = parent.props.value; + setContextCtx(parent, newValue); + } + parent = parent.parent; + } +} + +// ctxOldContext是 旧context提供者的context +function setVNodeOldContext(providerVNode: VNode, context: Object) { + providerVNode.setContext(CTX_OLD_CONTEXT, context); +} + +function getVNodeOldContext(vNode: VNode) { + return vNode.getContext(CTX_OLD_CONTEXT); +} + +function setOldContextCtx(providerVNode: VNode, context: Object) { + setVNodeOldContext(providerVNode, context); + ctxOldContext = context; +} + +function getOldContextCtx() { + return ctxOldContext; +} + +function resetOldContextCtx(vNode: VNode) { + ctxOldContext = getVNodeOldContext(vNode); +} + +function setVNodeOldPreviousContext(providerVNode: VNode, context: Object) { + providerVNode.setContext(CTX_OLD_PREVIOUS_CONTEXT, context); +} + +function getVNodeOldPreviousContext(vNode: VNode) { + return vNode.getContext(CTX_OLD_PREVIOUS_CONTEXT); +} + +function setOldPreviousContextCtx(context: Object) { + ctxOldPreviousContext = context; +} + +function getOldPreviousContextCtx() { + return ctxOldPreviousContext; +} + +function setContextChangeCtx(providerVNode: VNode, didChange: boolean) { + providerVNode.setContext(CTX_OLD_CHANGE, didChange); + ctxOldChange = didChange; +} + +function getContextChangeCtx() { + return ctxOldChange; +} + +function resetContextChangeCtx(vNode: VNode) { + ctxOldChange = vNode.getContext(CTX_OLD_CHANGE); +} + +export { + getNamespaceCtx, + resetNamespaceCtx, + setNamespaceCtx, + setContextCtx, + resetContextCtx, + recoverParentsContextCtx, + setOldContextCtx, + getOldContextCtx, + resetOldContextCtx, + setContextChangeCtx, + getContextChangeCtx, + resetContextChangeCtx, + setOldPreviousContextCtx, + getOldPreviousContextCtx, + setVNodeOldContext, + getVNodeOldContext, + setVNodeOldPreviousContext, + getVNodeOldPreviousContext, +}; + + diff --git a/libs/horizon/src/renderer/ErrorHandler.ts b/libs/horizon/src/renderer/ErrorHandler.ts new file mode 100644 index 00000000..f4e66484 --- /dev/null +++ b/libs/horizon/src/renderer/ErrorHandler.ts @@ -0,0 +1,189 @@ +/** + * 异常错误处理 + */ + +import type {VNode} from './Types'; +import type {Update} from './UpdateHandler'; + +import {ClassComponent, TreeRoot} from './vnode/VNodeTags'; +import {FlagUtils, Interrupted} from './vnode/VNodeFlags'; +import {newUpdate, UpdateState, pushUpdate} from './UpdateHandler'; +import {launchUpdateFromVNode, setBuildResultError, tryRenderRoot} from './TreeBuilder'; +import {setRootThrowError} from './submit/Submit'; +import {handleSuspenseChildThrowError} from './render/SuspenseComponent'; +import {updateShouldUpdateOfTree} from './vnode/VNodeShouldUpdate'; + +// 处理capture和bubble阶段抛出的错误 +export function handleRenderThrowError( + sourceVNode: VNode, + error: any, +) { + // vNode抛出了异常,标记Interrupted中断 + FlagUtils.markInterrupted(sourceVNode); + // dirtyNodes 不再有效 + sourceVNode.dirtyNodes = []; + + // error是个promise + if (error !== null && typeof error === 'object' && typeof error.then === 'function') { + // 抛出异常的节点,向上寻找,是否有suspense组件 + const foundSuspense = handleSuspenseChildThrowError(sourceVNode.parent, sourceVNode, error); + if (foundSuspense) { + return; + } + } + + // 抛出错误无法作为suspense内容处理(或无suspense来处理),这次当成真的错误来处理 + setBuildResultError(); + + // 向上遍历寻找ClassComponent组件(同时也是Error Boundaries组件) 或者 TreeRoot + let vNode = sourceVNode.parent; + do { + switch (vNode.tag) { + case TreeRoot: { + vNode.shouldUpdate = true; + launchUpdateFromVNode(vNode); + handleRootError(error); + return; + } + case ClassComponent: + const ctor = vNode.type; + const instance = vNode.realNode; + if ( + !vNode.flags.DidCapture && + ( + typeof ctor.getDerivedStateFromError === 'function' || + (instance !== null && typeof instance.componentDidCatch === 'function') + ) + ) { + FlagUtils.markShouldCapture(vNode); + + // Class捕捉到异常,触发一次刷新 + const update = createClassErrorUpdate(vNode, error); + pushUpdate(vNode, update); + + launchUpdateFromVNode(vNode); + + // 有异常处理类,把抛出异常的节点的Interrupted标志去掉,继续走正常的绘制流程 + FlagUtils.removeFlag(sourceVNode, Interrupted); + + return; + } + break; + default: + break; + } + vNode = vNode.parent; + } while (vNode !== null); +} + +// 处理submit阶段的异常 +export function handleSubmitError(vNode: VNode, error: any) { + if (vNode.tag === TreeRoot) { + handleRootError(error); + return; + } + + let node = vNode.parent; + // 向上遍历 + while (node !== null) { + if (node.tag === TreeRoot) { + handleRootError(error); + return; + } else if (node.tag === ClassComponent) { // 只有 class 组件才可以成为错误边界组件 + const ctor = node.type; + const instance = node.realNode; + if ( + typeof ctor.getDerivedStateFromError === 'function' || + typeof instance.componentDidCatch === 'function' + ) { + const getDerivedStateFromError = node.type.getDerivedStateFromError; + if (typeof getDerivedStateFromError === 'function') { + // 打印错误 + consoleError(error); + + const retState = getDerivedStateFromError(error); + if (retState) { // 有返回值 + // 触发更新 + triggerUpdate(node, retState); + } + } + + // 处理componentDidCatch + if (instance !== null && typeof instance.componentDidCatch === 'function') { + if (typeof getDerivedStateFromError !== 'function') { // 没有getDerivedStateFromError + // 打印错误 + consoleError(error); + } + + instance.componentDidCatch(error, { + componentStack: '', + }); + } + + return; + } + } + node = node.parent; + } +} + +function createClassErrorUpdate( + vNode: VNode, + error: any, +): Update { + const update = newUpdate(); + update.type = UpdateState.Error; + + const getDerivedStateFromError = vNode.type.getDerivedStateFromError; + if (typeof getDerivedStateFromError === 'function') { + update.content = () => { + consoleError(error); + return getDerivedStateFromError(error); + }; + } + + const inst = vNode.realNode; + if (inst !== null && typeof inst.componentDidCatch === 'function') { + update.callback = function callback() { + if (typeof getDerivedStateFromError !== 'function') { + // 打印错误 + consoleError(error); + } + + // @ts-ignore + this.componentDidCatch(error, { + componentStack: '', + }); + }; + } + return update; +} + +// 新增一个update,并且触发调度 +function triggerUpdate(vNode, state) { + const update = newUpdate(); + update.content = state; + pushUpdate(vNode, update); + + const root = updateShouldUpdateOfTree(vNode); + if (root !== null) { + tryRenderRoot(root); + } +} + +function handleRootError( + error: any, +) { + // 注意:如果根节点抛出错误,不会销毁整棵树,只打印日志,抛出异常。 + setRootThrowError(error); + consoleError(error); +} + +function consoleError(error: any): void { + if (isDev) { + // 只打印message为了让测试用例能pass + console['error']('The codes throw the error: ' + error.message); + } else { + console['error'](error); + } +} diff --git a/libs/horizon/src/renderer/ExecuteMode.ts b/libs/horizon/src/renderer/ExecuteMode.ts new file mode 100644 index 00000000..880e9374 --- /dev/null +++ b/libs/horizon/src/renderer/ExecuteMode.ts @@ -0,0 +1,33 @@ + +export const ByAsync = 'BY_ASYNC'; +export const BySync = 'BY_SYNC'; +export const InRender = 'IN_RENDER'; + +type RenderMode = typeof ByAsync | typeof BySync | typeof InRender; + +// 当前执行阶段标记 +let executeMode = { + [ByAsync]: false, + [BySync]: false, + [InRender]: false, +}; + +export function changeMode(mode: RenderMode, state = true) { + executeMode[mode] = state; +} + +export function checkMode(mode: RenderMode) { + return executeMode[mode]; +} + +export function isExecuting() { + return executeMode[ByAsync] || executeMode[BySync] || executeMode[InRender]; +} + +export function copyExecuteMode() { + return {...executeMode}; +} + +export function setExecuteMode(mode: typeof executeMode) { + executeMode = mode; +} diff --git a/libs/horizon/src/renderer/Renderer.ts b/libs/horizon/src/renderer/Renderer.ts new file mode 100644 index 00000000..85fc3d4b --- /dev/null +++ b/libs/horizon/src/renderer/Renderer.ts @@ -0,0 +1,48 @@ +import type {VNode} from './Types'; +import type {Update} from './UpdateHandler'; + +import { + asyncUpdates, + syncUpdates, + runDiscreteUpdates, + launchUpdateFromVNode, +} from './TreeBuilder'; +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 { + asyncUpdates, + syncUpdates, + runDiscreteUpdates, + runAsyncEffects, +}; + +export function startUpdate( + element: any, + treeRoot: VNode, + callback?: Callback, +) { + const update: Update = newUpdate(); + update.content = {element}; + + if (typeof callback === 'function') { + update.callback = callback; + } + + pushUpdate(treeRoot, update); + + launchUpdateFromVNode(treeRoot); +} + +export function getFirstCustomDom(treeRoot: VNode): Element | Text | null { + const firstChild = getFirstChild(treeRoot); + if (!firstChild) { + return null; + } + + return firstChild.realNode; +} + diff --git a/libs/horizon/src/renderer/Types.ts b/libs/horizon/src/renderer/Types.ts new file mode 100644 index 00000000..b1732fd1 --- /dev/null +++ b/libs/horizon/src/renderer/Types.ts @@ -0,0 +1,58 @@ +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>] +}; +export type UseReducerHookType = { + useReducer( + reducer: (S, A) => S, + initArg: P, init?: (P) => S, + ): [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 ProviderType = { + vtype: number; + _context: ContextType; +}; + +export type ContextType = { + vtype: number; + Consumer: ContextType; + Provider: ProviderType; + value: T; +}; + +export type PortalType = { + vtype: number; + key: null | string; + outerDom: any; + children: any; +}; + +export type RefType = { + current: any; +}; + +export interface PromiseType { + then( + onFulfill: (value: R) => void | PromiseType | U, + onReject: (error: any) => void | PromiseType | U, + ): void | PromiseType; +} + diff --git a/libs/horizon/src/renderer/utils/compare.ts b/libs/horizon/src/renderer/utils/compare.ts new file mode 100644 index 00000000..3849ec85 --- /dev/null +++ b/libs/horizon/src/renderer/utils/compare.ts @@ -0,0 +1,51 @@ +/** + * 兼容IE浏览器没有Object.is + */ +export function isSame(x: any, y: any) { + if (!(typeof Object.is === 'function')) { + if (x === y) { + // +0 != -0 + return x !== 0 || 1 / x === 1 / y; + } else { + // NaN == NaN + return x !== x && y !== y; + } + } else { + return Object.is(x, y); + } +} + +export function isArrayEqual(nextParam: Array, lastParam: Array | null) { + if (lastParam === null || lastParam.length !== nextParam.length) { + return false; + } + for (let i = 0; i < lastParam.length; i++) { + if (!isSame(nextParam[i], lastParam[i])) { + return false; + } + } + return true; +} + +export function shallowCompare(paramX: any, paramY: any): boolean { + if (isSame(paramX, paramY)) { + return true; + } + + // 对比对象 + if (typeof paramX === 'object' && typeof paramY === 'object' && paramX !== null && paramY !== null) { + const keysX = Object.keys(paramX); + const keysY = Object.keys(paramY); + + // key长度不相等时直接返回不相等 + if (keysX.length !== keysY.length) { + return false; + } + + return keysX.every((key, i) => { + return Object.prototype.hasOwnProperty.call(paramY, key) && isSame(paramX[key], paramY[keysX[i]]); + }); + } + + return false; +} diff --git a/libs/horizon/src/renderer/utils/elementType.ts b/libs/horizon/src/renderer/utils/elementType.ts new file mode 100644 index 00000000..b23ad4d6 --- /dev/null +++ b/libs/horizon/src/renderer/utils/elementType.ts @@ -0,0 +1,11 @@ +export const TYPE_ELEMENT = 1; +export const TYPE_PORTAL = 2; +export const TYPE_FRAGMENT = 3; +export const TYPE_STRICT_MODE = 4; +export const TYPE_PROVIDER = 5; +export const TYPE_CONTEXT = 6; +export const TYPE_FORWARD_REF = 7; +export const TYPE_SUSPENSE = 8; +export const TYPE_PROFILER = 9; +export const TYPE_MEMO = 10; +export const TYPE_LAZY = 11; diff --git a/libs/horizon/src/renderer/utils/throwIfTrue.ts b/libs/horizon/src/renderer/utils/throwIfTrue.ts new file mode 100644 index 00000000..5cf8522e --- /dev/null +++ b/libs/horizon/src/renderer/utils/throwIfTrue.ts @@ -0,0 +1,13 @@ +// 当条件不成立报错 +// 接收模板 +export function throwIfTrue(condition: boolean, errTemplate: string, ...errExpressions: string[]) { + if (condition) { + // 将%s 替换成对应的变量 + const msg = errTemplate.split('%s').reduce((prevSentence: string, part: string, idx: number) => { + // %s对应的变量 + const expression = idx < errExpressions.length ? errExpressions[idx] : '' ; + return prevSentence + part + expression; + }, ''); + throw Error(msg); + } +} diff --git a/libs/horizon/src/renderer/vnode/ProcessingVNode.ts b/libs/horizon/src/renderer/vnode/ProcessingVNode.ts new file mode 100644 index 00000000..1110242c --- /dev/null +++ b/libs/horizon/src/renderer/vnode/ProcessingVNode.ts @@ -0,0 +1,8 @@ +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 new file mode 100644 index 00000000..f726c4a4 --- /dev/null +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -0,0 +1,98 @@ +/** + * 虚拟DOM结构体 + */ +import {TreeRoot} from './VNodeTags'; +import type {VNodeTag} from './VNodeTags'; +import type {RefType, ContextType} from '../Types'; +import type {Hook} from '../hooks/HookType'; + +export class VNode { + tag: VNodeTag; + key: string | null; // 唯一标识符 + type: any = null; + realNode: any = null; // 如果是类,则存放实例;如果是div这种,则存放真实DOM; + + // 关系结构 + parent: VNode | null = null; // 父节点 + children: Array | null = null; // 子节点 + cIndex: number = 0; // 节点在children数组中的位置 + eIndex: number = 0; // HorizonElement在jsx中的位置,例如:jsx中的null不会生成vNode,所以eIndex和cIndex不一致 + + ref: RefType | ((handle: any) => void) | null = null; // 包裹一个函数,submit阶段使用,比如将外部useRef生成的对象赋值到ref上 + props: any; // 传给组件的props的值,类组件包含defaultProps,Lazy组件不包含 + oldProps: any = null; + + suspensePromises: any = null; // suspense组件的promise列表 + changeList: any = null; // DOM的变更列表 + effectList: any[] = []; // useEffect 的更新数组 + updates: any[] = null; // TreeRoot和ClassComponent使用的更新数组 + stateCallbacks: any[] = []; // 存放存在setState的第二个参数和HorizonDOM.render的第三个参数所在的node数组 + isForceUpdate: boolean = false; // 是否使用强制更新 + + state: any = null; // ClassComponent和TreeRoot的状态 + hooks: Array> = []; // 保存hook + suspenseChildStatus: string = ''; // Suspense的Children是否显示 + depContexts: Array> = []; // FunctionComponent和ClassComponent对context的依赖列表 + isDepContextChange: boolean = false; // context是否变更 + dirtyNodes: Array = []; // 需要改动的节点数组 + shouldUpdate: boolean = false; + childShouldUpdate: boolean = false; + outerDom: any; + task: any; + + // 使用这个变量来记录修改前的值,用于恢复。 + contexts = {}; + // 因为LazyComponent会修改tag和type属性,为了能识别,增加一个属性 + isLazyComponent: boolean = false; + + // 因为LazyComponent会修改type属性,为了在diff中判断是否可以复用,需要增加一个lazyType + lazyType: any = null; + flags: { + Addition?: boolean, + Update?: boolean, + Deletion?: boolean, + ResetText?: boolean, + Callback?: boolean, + DidCapture?: boolean, + Ref?: boolean, + Snapshot?: boolean, + Interrupted?: boolean, + ShouldCapture?: boolean, + ForceUpdate?: boolean, + } = {}; + + // one tree相关属性 + isCreated: boolean = true; + oldHooks: Array> = []; // 保存上一次执行的hook + oldState: any = null; + oldRef: RefType | ((handle: any) => void) | null = null; + suspenseChildThrow = false; + oldSuspenseChildStatus: string = ''; // 上一次Suspense的Children是否显示 + oldChildren: Array | null = null; + suspenseDidCapture: boolean = false; // suspense是否捕获了异常 + promiseResolve: boolean = false; // suspense的promise是否resolve + + path: Array = []; // 保存从根到本节点的路径 + toUpdateNodes: Set | null = null; // 保存要更新的节点 + + constructor(tag: VNodeTag, props: any, key: null | string, outerDom) { + this.tag = tag; // 对应组件的类型,比如ClassComponent等 + this.key = key; + + this.props = props; + + // 根节点 + if (tag === TreeRoot) { + this.outerDom = outerDom; + this.task = null; + this.toUpdateNodes = new Set(); + } + } + + setContext(contextName, value) { + this.contexts[contextName] = value; + } + getContext(contextName) { + return this.contexts[contextName]; + } +} diff --git a/libs/horizon/src/renderer/vnode/VNodeFlags.ts b/libs/horizon/src/renderer/vnode/VNodeFlags.ts new file mode 100644 index 00000000..7259beb7 --- /dev/null +++ b/libs/horizon/src/renderer/vnode/VNodeFlags.ts @@ -0,0 +1,92 @@ +/** + * vNode结构的变化标志 + */ + +import type { VNode } from '../Types'; + +// vNode节点的flags +export const Addition = 'Addition'; +export const Update = 'Update'; +export const Deletion = 'Deletion'; +export const ResetText = 'ResetText'; +export const Callback = 'Callback'; +export const DidCapture = 'DidCapture'; +export const Ref = 'Ref'; +export const Snapshot = 'Snapshot'; +// 被中断了,抛出错误的vNode以及它的父vNode +export const Interrupted = 'Interrupted'; +export const ShouldCapture = 'ShouldCapture'; +// For suspense +export const ForceUpdate = 'ForceUpdate'; + +const flagArr = [Addition, Update, Deletion, ResetText, Callback, DidCapture, Ref, Snapshot, Interrupted, ShouldCapture, ForceUpdate]; + +const LifecycleEffectArr = [Update, Callback, Ref, Snapshot]; + +function resetFlag(node) { + node.flags = {}; +} + +export class FlagUtils { + static removeFlag(node: VNode, flag: string) { + node.flags[flag] = false; + } + static removeLifecycleEffectFlags(node) { + LifecycleEffectArr.forEach(key => { + node.flags[key] = false; + }); + } + static hasAnyFlag(node: VNode) { // 有标志位 + let keyFlag = false; + flagArr.forEach(key => { + if (node.flags[key]) { + keyFlag = true; + } + }); + return keyFlag; + } + + static setNoFlags(node: VNode) { + resetFlag(node); + } + + static markAddition(node: VNode) { + node.flags.Addition = true; + } + static setAddition(node: VNode) { + resetFlag(node); + node.flags.Addition = true; + } + static markUpdate(node: VNode) { + node.flags.Update = true; + } + static setDeletion(node: VNode) { + resetFlag(node); + node.flags.Deletion = true; + } + static markContentReset(node: VNode) { + node.flags.ResetText = true; + } + static markCallback(node: VNode) { + node.flags.Callback = true; + } + static markDidCapture(node: VNode) { + node.flags.DidCapture = true; + } + static markShouldCapture(node: VNode) { + node.flags.ShouldCapture = true; + } + static markRef(node: VNode) { + node.flags.Ref = true; + } + static markSnapshot(node: VNode) { + node.flags.Snapshot = true; + } + static markInterrupted(node: VNode) { + node.flags.Interrupted = true; + } + static markForceUpdate(node: VNode) { + node.flags.ForceUpdate = true; + } +} + diff --git a/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts b/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts new file mode 100644 index 00000000..00ebe158 --- /dev/null +++ b/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts @@ -0,0 +1,66 @@ +// 从当前节点向上遍历,更新shouldUpdate和childShouldUpdate +import {VNode} from './VNode'; +import {TreeRoot} from './VNodeTags'; + +export function updateShouldUpdateOfTree(vNode: VNode): VNode | null { + vNode.shouldUpdate = true; + + // 一直向上遍历,修改childShouldUpdate + let node = vNode; + let parent = vNode.parent; + while (parent !== null) { + parent.childShouldUpdate = true; + node = parent; + parent = parent.parent; + } + + if (node.tag === TreeRoot) { + node.shouldUpdate = true; + // 返回根节点 + return node; + } + + return null; +} + +// 设置节点的childShouldUpdate +export function updateChildShouldUpdate(vNode: VNode) { + const children = vNode.children || []; + + for (let i = 0; i < children.length; i++) { + const child = children[i]; + if (child && (child.shouldUpdate || child.childShouldUpdate)) { + vNode.childShouldUpdate = true; + return; + } + } + vNode.childShouldUpdate = false; +} + +// 设置节点的所有父节点的childShouldUpdate +export function updateParentsChildShouldUpdate(vNode: VNode) { + let node = vNode.parent; + let isShouldUpdate = vNode.shouldUpdate || vNode.childShouldUpdate; + + if (isShouldUpdate) { // 开始节点是shouldUpdate或childShouldUpdate + setParentsChildShouldUpdate(node); + } else { + while (node !== null) { + updateChildShouldUpdate(node); + node = node.parent; + } + } +} + +// 更新从当前节点到根节点的childShouldUpdate为true +export function setParentsChildShouldUpdate(parent: VNode | null) { + let node = parent; + while (node !== null) { + if (node.childShouldUpdate) { + break; + } + node.childShouldUpdate = true; + + node = node.parent; + } +} diff --git a/libs/horizon/src/renderer/vnode/VNodeTags.ts b/libs/horizon/src/renderer/vnode/VNodeTags.ts new file mode 100644 index 00000000..2ec1db13 --- /dev/null +++ b/libs/horizon/src/renderer/vnode/VNodeTags.ts @@ -0,0 +1,21 @@ +/** + * 定义vNode的类型 + */ +export type VNodeTag = string; + +export const TreeRoot = 'TreeRoot'; // tree的根节点,用于存放一些tree级的变量 +export const FunctionComponent = 'FunctionComponent'; +export const ClassComponent = 'ClassComponent'; +export const ClsOrFunComponent = 'ClsOrFunComponent'; +export const DomPortal = 'DomPortal'; +export const DomComponent = 'DomComponent'; +export const DomText = 'DomText'; +export const Fragment = 'Fragment'; +export const ContextConsumer = 'ContextConsumer'; +export const ContextProvider = 'ContextProvider'; +export const ForwardRef = 'ForwardRef'; +export const Profiler = 'Profiler'; +export const SuspenseComponent = 'SuspenseComponent'; +export const MemoComponent = 'MemoComponent'; +export const LazyComponent = 'LazyComponent'; +export const IncompleteClassComponent = 'IncompleteClassComponent'; diff --git a/libs/horizon/src/renderer/vnode/VNodeUtils.ts b/libs/horizon/src/renderer/vnode/VNodeUtils.ts new file mode 100644 index 00000000..3b921738 --- /dev/null +++ b/libs/horizon/src/renderer/vnode/VNodeUtils.ts @@ -0,0 +1,278 @@ +/** + * 提供:vNode的“遍历”,“查找”,“判断”的相关工具方法 + */ + +import type {VNode} from '../Types'; + +import {DomComponent, DomPortal, DomText, TreeRoot} from './VNodeTags'; +import {isComment} from '../../dom/utils/Common'; +import {getNearestVNode} from '../../dom/DOMInternalKeys'; + +export function getSiblingVNode(node) { + let siblingVNode = null; + const index = node.cIndex; + if (node && node.parent && node.parent.children && node.parent.children.length > index + 1) { + siblingVNode = node.parent.children[index + 1]; + } + return siblingVNode; +} + +export function getFirstChild(vNode: VNode) { + return (vNode.children && vNode.children.length) ? vNode.children[0] : null; +} + +// 从beginVNode开始深度遍历vNode树,对每个vNode调用handleVNode方法 +export function travelVNodeTree( + beginVNode: VNode, + handleVNode: Function, + childFilter: Function = () => false, // 返回true不处理child + finishVNode?: VNode, // 结束遍历节点,有时候和beginVNode不相同 + handleWhenToParent?: Function +): VNode | null { + const overVNode = finishVNode || beginVNode; + let node = beginVNode; + + while (true) { + const ret = handleVNode(node); + // 如果处理一个vNode时有返回值,则中断遍历 + if (ret) { + return ret; + } + + // 找子节点 + const childVNode = getFirstChild(node); + if (childVNode !== null && !childFilter(node)) { + childVNode.parent = node; + node = childVNode; + continue; + } + + // 回到开始节点 + if (node === overVNode) { + return null; + } + + // 找兄弟,没有就往上再找兄弟 + while (getSiblingVNode(node) === null) { + if (node.parent === null || node.parent === overVNode) { + return null; + } + node = node.parent; + + if (typeof handleWhenToParent === 'function') { + handleWhenToParent(node); + } + } + // 找到兄弟 + const siblingVNode = getSiblingVNode(node); + siblingVNode.parent = node.parent; + node = siblingVNode; + } +} + +// 置空vNode +export function clearVNode(vNode: VNode) { + clearOneVNode(vNode); +} + +function clearOneVNode(vNode: VNode) { + vNode.children = []; + vNode.depContexts = []; + vNode.dirtyNodes = []; + vNode.oldProps = null; + vNode.state = null; + vNode.hooks = []; + vNode.suspenseChildStatus = ''; + vNode.props = null; + vNode.parent = null; + vNode.suspensePromises = null; + vNode.changeList = null; + vNode.effectList = []; + vNode.updates = null; + + vNode.oldHooks = []; + vNode.oldState = null; + vNode.oldRef = null; + vNode.suspenseChildThrow = false; + vNode.oldSuspenseChildStatus = ''; + vNode.oldChildren = null; + + vNode.path = []; + vNode.toUpdateNodes = null; +} + +// 是dom类型的vNode +export function isDomVNode(node: VNode) { + return node.tag === DomComponent || node.tag === DomText; +} + +// 是容器类型的vNode +function isDomContainer(vNode: VNode): boolean { + return ( + vNode.tag === DomComponent || + vNode.tag === TreeRoot || + vNode.tag === DomPortal + ); +} + +// 找到DOM类型的父 +export function findDomParent(vNode: VNode) { + let parent = vNode.parent; + + while (parent !== null) { + switch (parent.tag) { + case DomComponent: + return {parent, parentDom: parent.realNode}; + case TreeRoot: + case DomPortal: + return {parent, parentDom: parent.outerDom}; + } + parent = parent.parent; + } + + return null; +} + +export function findDomVNode(vNode: VNode): VNode | null { + return travelVNodeTree(vNode, (node) => { + if (node.tag === DomComponent || node.tag === DomText) { + return node; + } + }); +} + +export function findDOMByClassInst(inst) { + const vNode = inst._vNode; + if (vNode === undefined) { + throw new Error('Unable to find the vNode by class instance.'); + } + + const domVNode = findDomVNode(vNode); + + return domVNode !== null ? domVNode.realNode : null; +} + +// 判断dom树是否已经挂载 +export function isMounted(vNode: VNode) { + const rootNode = getTreeRootVNode(vNode); + // 如果根节点是 Dom 类型节点,表示已经挂载 + return rootNode.tag === TreeRoot; +} + +function getTreeRootVNode(vNode) { + let node = vNode; + while (node.parent) { + node = node.parent; + } + return node; +} + +// 找到相邻的DOM +export function getSiblingDom(vNode: VNode): Element | null { + let node: VNode = vNode; + + findSibling: while (true) { + // 没有兄弟节点,找父节点 + while (getSiblingVNode(node) === null) { + // 没父节点,或父节点已经是根节点,则返回 + if (node.parent === null || isDomContainer(node.parent)) { + return null; + } + node = node.parent; + } + + const siblingVNode = getSiblingVNode(node); + siblingVNode.parent = node.parent; + node = siblingVNode; + + // 如果不是dom节点,往下找 + while (!isDomVNode(node)) { + // 如果节点也是Addition + if (node.flags.Addition) { + continue findSibling; + } + + // 没有子节点,或是DomPortal + if (!node.children || !node.children.length || node.tag === DomPortal) { + continue findSibling; + } else { + const childVNode = getFirstChild(node); + childVNode.parent = node; + node = childVNode; + } + } + + if (!node.flags.Addition) { + // 找到 + return node.realNode; + } + } +} + +function isSameContainer( + container: Element, + targetContainer: EventTarget, +): boolean { + if (container === targetContainer) { + return true; + } + // 注释类型的节点 + if (isComment(container) && container.parentNode === targetContainer) { + return true + } + return false; +} + +function isPortalRoot(vNode, targetContainer) { + if (vNode.tag === DomPortal) { + let topVNode = vNode.parent; + while (topVNode !== null) { + const grandTag = topVNode.tag; + if (grandTag === TreeRoot || grandTag === DomPortal) { + const topContainer = topVNode.outerDom; + // 如果topContainer是targetContainer,不需要在这里处理 + if (isSameContainer(topContainer, targetContainer)) { + return true; + } + } + topVNode = topVNode.parent; + } + return false; + } + return false; +} + +// 获取根vNode节点 +export function getExactNode(targetVNode, targetContainer) { + // 确认vNode节点是否准确,portal场景下可能祖先节点不准确 + let vNode = targetVNode; + while (vNode !== null) { + if (vNode.tag === TreeRoot || vNode.tag === DomPortal) { + let container = vNode.outerDom; + if (isSameContainer(container, targetContainer)) { + break; + } + if (isPortalRoot(vNode, targetContainer)) { + return null; + } + while (container !== null) { + const parentNode = getNearestVNode(container); + if (parentNode === null) { + return null; + } + if (parentNode.tag === DomComponent || parentNode.tag === DomText) { + vNode = parentNode; + return getExactNode(vNode, targetContainer); + } + container = container.parentNode; + } + } + vNode = vNode.parent; + } + if (vNode === null) { + return null; + } + return targetVNode; +} + +