From 6d5f3c6888b3767e442cc748bf4c811b1ac8d4c4 Mon Sep 17 00:00:00 2001 From: * <8> Date: Sat, 25 Dec 2021 15:07:38 +0800 Subject: [PATCH 1/3] Match-id-36a3aeae070795ceee07c5bd46be79ae9649ebc2 --- .../renderer/components/BaseClassComponent.ts | 13 ++++--- libs/horizon/src/renderer/components/Lazy.ts | 2 +- libs/horizon/src/renderer/hooks/BaseHook.ts | 37 ++++++++++++------- libs/horizon/src/renderer/hooks/HookMain.ts | 16 ++++---- libs/horizon/src/renderer/hooks/HookType.ts | 1 - .../src/renderer/hooks/UseEffectHook.ts | 14 +++---- .../src/renderer/hooks/UseReducerHook.ts | 3 +- .../src/renderer/taskExecutor/TaskExecutor.ts | 2 +- path.json | 3 -- 9 files changed, 48 insertions(+), 43 deletions(-) delete mode 100644 path.json diff --git a/libs/horizon/src/renderer/components/BaseClassComponent.ts b/libs/horizon/src/renderer/components/BaseClassComponent.ts index 12dab04e..ec141650 100644 --- a/libs/horizon/src/renderer/components/BaseClassComponent.ts +++ b/libs/horizon/src/renderer/components/BaseClassComponent.ts @@ -1,11 +1,12 @@ /** * Component的api setState和forceUpdate在实例生成阶段实现 */ -class Component { - props; - context; +class Component { + props: P; + context: C; + state: S; - constructor(props, context) { + constructor(props: P, context: C) { this.props = props; this.context = context; } @@ -16,8 +17,8 @@ Component.prototype.isReactComponent = true; /** * 支持PureComponent */ -class PureComponent extends Component { - constructor(props, context) { +class PureComponent extends Component { + constructor(props: P, context: C) { super(props, context); } } diff --git a/libs/horizon/src/renderer/components/Lazy.ts b/libs/horizon/src/renderer/components/Lazy.ts index 55a276c5..4b37091e 100644 --- a/libs/horizon/src/renderer/components/Lazy.ts +++ b/libs/horizon/src/renderer/components/Lazy.ts @@ -15,7 +15,7 @@ type LazyContent = { }; export type LazyComponent = { - vtype: Symbol | number, + vtype: number, _content: P, _load: (content: P) => T, }; diff --git a/libs/horizon/src/renderer/hooks/BaseHook.ts b/libs/horizon/src/renderer/hooks/BaseHook.ts index ffbd3e6a..18e59db1 100644 --- a/libs/horizon/src/renderer/hooks/BaseHook.ts +++ b/libs/horizon/src/renderer/hooks/BaseHook.ts @@ -3,7 +3,8 @@ import type {Hook} from './HookType'; let processingVNode: VNode = null; -let activatedHook: Hook | null = null; +// lastTimeHook是上一次执行func时产生的hooks中,与currentHook对应的hook +let lastTimeHook: Hook | null = null; // 当前hook函数对应的hook对象 let currentHook: Hook | null = null; @@ -16,12 +17,12 @@ export function setProcessingVNode(vNode: VNode) { processingVNode = vNode; } -export function getActivatedHook() { - return activatedHook; +export function getLastTimeHook() { + return lastTimeHook; } -export function setActivatedHook(hook: Hook) { - activatedHook = hook; +export function setLastTimeHook(hook: Hook) { + lastTimeHook = hook; } export function setCurrentHook(hook: Hook) { @@ -47,24 +48,32 @@ export function createHook(state: any = null): Hook { return currentHook; } -export function getNextHook(hook: Hook, vNode: VNode) { - return vNode.hooks[hook.hIndex + 1] || null; +export function getNextHook(hook: Hook, hooks: Array>) { + return hooks[hook.hIndex + 1] || null; } // 获取当前hook函数对应的hook对象。 -// processing中的hook和activated中的hook,需要同时往前走, -// 原因:1.比对hook的数量有没有变化(非必要);2.从activated中的hook获取removeEffect +// processing中的hook和上一次执行中的hook,需要同时往前走, +// 原因:1.比对hook的数量有没有变化(非必要);2.从上一次执行中的hook获取removeEffect export function getCurrentHook(): Hook { - currentHook = currentHook !== null ? getNextHook(currentHook, processingVNode) : (processingVNode.hooks[0] || null); - const activated = processingVNode.twins; - activatedHook = activatedHook !== null ? getNextHook(activatedHook, activated) : ((activated && activated.hooks[0]) || null); + currentHook = currentHook !== null ? getNextHook(currentHook, processingVNode.hooks) : (processingVNode.hooks[0] || null); + + if (lastTimeHook !== null) { + lastTimeHook = getNextHook(lastTimeHook, processingVNode.oldHooks); + } else { + if (processingVNode.oldHooks && processingVNode.oldHooks.length) { + lastTimeHook = processingVNode.oldHooks[0]; + } else { + lastTimeHook = null; + } + } if (currentHook === null) { - if (activatedHook === null) { + if (lastTimeHook === null) { throw Error('Hooks are more than expected, please check whether the hook is written in the condition.'); } - createHook(activatedHook.state); + createHook(lastTimeHook.state); } return currentHook; diff --git a/libs/horizon/src/renderer/hooks/HookMain.ts b/libs/horizon/src/renderer/hooks/HookMain.ts index 713696d3..e8992e5a 100644 --- a/libs/horizon/src/renderer/hooks/HookMain.ts +++ b/libs/horizon/src/renderer/hooks/HookMain.ts @@ -9,9 +9,9 @@ const { import {getNewContext} from '../components/context/Context'; import { - getActivatedHook, + getLastTimeHook, getProcessingVNode, - setActivatedHook, + setLastTimeHook, setProcessingVNode, setCurrentHook, getNextHook } from './BaseHook'; @@ -24,7 +24,6 @@ export function exeFunctionHook( funcComp: (props: Object, arg: Object) => any, props: Object, arg: Object, - activated: VNode | null, processing: VNode, ): any { // 重置全局变量 @@ -35,11 +34,12 @@ export function exeFunctionHook( setProcessingVNode(processing); + processing.oldHooks = processing.hooks; processing.hooks = []; processing.effectList = []; // 设置hook阶段 - if (activated === null || !activated.hooks.length) { + if (processing.isCreated || !processing.oldHooks.length) { setHookStage(HookStage.Init); } else { setHookStage(HookStage.Update); @@ -51,9 +51,9 @@ export function exeFunctionHook( setHookStage(null); // 判断hook是否写在了if条件中,如果在if中会出现数量不对等的情况 - const activatedHook = getActivatedHook(); - if (activatedHook !== null) { - if (getNextHook(getActivatedHook(), activated) !== null) { + const lastTimeHook = getLastTimeHook(); + if (lastTimeHook !== null) { + if (getNextHook(getLastTimeHook(), processing.oldHooks) !== null) { throw Error('Hooks are less than expected, please check whether the hook is written in the condition.'); } } @@ -67,7 +67,7 @@ export function exeFunctionHook( function resetGlobalVariable() { setHookStage(null); setProcessingVNode(null); - setActivatedHook(null); + setLastTimeHook(null); setCurrentHook(null); } diff --git a/libs/horizon/src/renderer/hooks/HookType.ts b/libs/horizon/src/renderer/hooks/HookType.ts index 984b5ecd..e965fdf1 100644 --- a/libs/horizon/src/renderer/hooks/HookType.ts +++ b/libs/horizon/src/renderer/hooks/HookType.ts @@ -1,5 +1,4 @@ import {EffectConstant} from './EffectConstant'; -import {VNode} from '../Types'; export interface Hook { state: Reducer | Effect | Memo | CallBack | Ref; diff --git a/libs/horizon/src/renderer/hooks/UseEffectHook.ts b/libs/horizon/src/renderer/hooks/UseEffectHook.ts index c777f860..ccd4e7d4 100644 --- a/libs/horizon/src/renderer/hooks/UseEffectHook.ts +++ b/libs/horizon/src/renderer/hooks/UseEffectHook.ts @@ -1,7 +1,7 @@ import { createHook, getCurrentHook, - getActivatedHook, + getLastTimeHook, getProcessingVNode, throwNotInFuncError } from './BaseHook'; import {FlagUtils} from '../vnode/VNodeFlags'; @@ -38,10 +38,10 @@ function useEffect( } export function useEffectForInit(effectFunc, deps, effectType): void { - const hook = createHook(); - const nextDeps = deps !== undefined ? deps : null; FlagUtils.markUpdate(getProcessingVNode()); + const hook = createHook(); + const nextDeps = deps !== undefined ? deps : null; // 初始阶段,设置DepsChange标记位; 构造EffectList数组,并赋值给state hook.state = createEffect(effectFunc, undefined, nextDeps, EffectConstant.DepsChange | effectType); } @@ -51,13 +51,13 @@ export function useEffectForUpdate(effectFunc, deps, effectType): void { const nextDeps = deps !== undefined ? deps : null; let removeFunc; - if (getActivatedHook() !== null) { - const effect = getActivatedHook().state as Effect; - // removeEffect是通过执行effect返回的,所以需要在activated中获取 + if (getLastTimeHook() !== null) { + const effect = getLastTimeHook().state as Effect; + // removeEffect是通过执行effect返回的,所以需要在上一次hook中获取 removeFunc = effect.removeEffect; const lastDeps = effect.dependencies; - // 判断dependencies是否相同,不同就不设置DepsChange标记位 + // 判断dependencies是否相同,同相同不需要设置DepsChange标记位 if (nextDeps !== null && isArrayEqual(nextDeps, lastDeps)) { hook.state = createEffect(effectFunc, removeFunc, nextDeps, effectType); return; diff --git a/libs/horizon/src/renderer/hooks/UseReducerHook.ts b/libs/horizon/src/renderer/hooks/UseReducerHook.ts index 844afb0a..4c241152 100644 --- a/libs/horizon/src/renderer/hooks/UseReducerHook.ts +++ b/libs/horizon/src/renderer/hooks/UseReducerHook.ts @@ -54,10 +54,9 @@ function insertUpdate(action: A, hook: Hook): Update { // setState, setReducer触发函数 export function TriggerAction(vNode: VNode, hook: Hook, action: A) { const newUpdate = insertUpdate(action, hook); - const twins = vNode.twins; // 判断是否需要刷新 - if (!vNode.shouldUpdate && (twins === null || !twins.shouldUpdate)) { + if (!vNode.shouldUpdate) { const reducerObj = hook.state as Reducer; const { stateValue, reducer } = reducerObj; diff --git a/libs/horizon/src/renderer/taskExecutor/TaskExecutor.ts b/libs/horizon/src/renderer/taskExecutor/TaskExecutor.ts index 49e0c98d..f5a8b986 100644 --- a/libs/horizon/src/renderer/taskExecutor/TaskExecutor.ts +++ b/libs/horizon/src/renderer/taskExecutor/TaskExecutor.ts @@ -80,7 +80,7 @@ function callTasks(initialTime) { task.expirationTime > currentTime && isOverTime() ) { - // 没到deadline + // 任务的过期时间大于当前时间(没达到此任务的过期时间)且超过了deadline break; } diff --git a/path.json b/path.json deleted file mode 100644 index 8540fb5e..00000000 --- a/path.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "horizon-test-path": "../horizon-test" -} From d217302bf154a47c4da00f0015179dcf0db99cb4 Mon Sep 17 00:00:00 2001 From: * <8> Date: Sat, 25 Dec 2021 15:08:34 +0800 Subject: [PATCH 2/3] Match-id-6bc4f99ab260e07592cabc5ef99f2985d3befb0e --- libs/horizon/src/renderer/ContextSaver.ts | 147 +++++++++ libs/horizon/src/renderer/ErrorHandler.ts | 189 ++++++++++++ libs/horizon/src/renderer/ExecuteMode.ts | 33 +++ libs/horizon/src/renderer/Renderer.ts | 48 +++ libs/horizon/src/renderer/Types.ts | 58 ++++ libs/horizon/src/renderer/utils/compare.ts | 51 ++++ .../horizon/src/renderer/utils/elementType.ts | 11 + .../horizon/src/renderer/utils/throwIfTrue.ts | 13 + .../src/renderer/vnode/ProcessingVNode.ts | 8 + libs/horizon/src/renderer/vnode/VNode.ts | 98 ++++++ libs/horizon/src/renderer/vnode/VNodeFlags.ts | 92 ++++++ .../src/renderer/vnode/VNodeShouldUpdate.ts | 66 +++++ libs/horizon/src/renderer/vnode/VNodeTags.ts | 21 ++ libs/horizon/src/renderer/vnode/VNodeUtils.ts | 278 ++++++++++++++++++ 14 files changed, 1113 insertions(+) create mode 100644 libs/horizon/src/renderer/ContextSaver.ts create mode 100644 libs/horizon/src/renderer/ErrorHandler.ts create mode 100644 libs/horizon/src/renderer/ExecuteMode.ts create mode 100644 libs/horizon/src/renderer/Renderer.ts create mode 100644 libs/horizon/src/renderer/Types.ts create mode 100644 libs/horizon/src/renderer/utils/compare.ts create mode 100644 libs/horizon/src/renderer/utils/elementType.ts create mode 100644 libs/horizon/src/renderer/utils/throwIfTrue.ts create mode 100644 libs/horizon/src/renderer/vnode/ProcessingVNode.ts create mode 100644 libs/horizon/src/renderer/vnode/VNode.ts create mode 100644 libs/horizon/src/renderer/vnode/VNodeFlags.ts create mode 100644 libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts create mode 100644 libs/horizon/src/renderer/vnode/VNodeTags.ts create mode 100644 libs/horizon/src/renderer/vnode/VNodeUtils.ts 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; +} + + From c766853a8941ceaa4cc5e60581bf655b3c4f65d0 Mon Sep 17 00:00:00 2001 From: * <8> Date: Sat, 25 Dec 2021 15:09:41 +0800 Subject: [PATCH 3/3] Match-id-a1095c6e547ae06557735fa486ddd1994eed8c20 --- .../src/renderer/vnode/ProcessingVNode.ts | 8 - libs/horizon/src/renderer/vnode/VNode.ts | 98 ------ .../src/renderer/vnode/VNodeCreator.ts | 191 ------------ libs/horizon/src/renderer/vnode/VNodeFlags.ts | 92 ------ .../src/renderer/vnode/VNodeShouldUpdate.ts | 66 ----- libs/horizon/src/renderer/vnode/VNodeTags.ts | 21 -- libs/horizon/src/renderer/vnode/VNodeUtils.ts | 278 ------------------ 7 files changed, 754 deletions(-) delete mode 100644 libs/horizon/src/renderer/vnode/ProcessingVNode.ts delete mode 100644 libs/horizon/src/renderer/vnode/VNode.ts delete mode 100644 libs/horizon/src/renderer/vnode/VNodeCreator.ts delete mode 100644 libs/horizon/src/renderer/vnode/VNodeFlags.ts delete mode 100644 libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts delete mode 100644 libs/horizon/src/renderer/vnode/VNodeTags.ts delete mode 100644 libs/horizon/src/renderer/vnode/VNodeUtils.ts 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 deleted file mode 100644 index f726c4a4..00000000 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ /dev/null @@ -1,98 +0,0 @@ -/** - * 虚拟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/VNodeCreator.ts b/libs/horizon/src/renderer/vnode/VNodeCreator.ts deleted file mode 100644 index e648e3b1..00000000 --- a/libs/horizon/src/renderer/vnode/VNodeCreator.ts +++ /dev/null @@ -1,191 +0,0 @@ -import type { VNodeTag } from './VNodeTags'; -import { FlagUtils } from './VNodeFlags'; -import { - ClassComponent, - ContextConsumer, - ContextProvider, - ForwardRef, - Fragment, - FunctionComponent, - DomComponent, - DomPortal, - TreeRoot, - DomText, - ClsOrFunComponent, - LazyComponent, - MemoComponent, - SuspenseComponent, -} from './VNodeTags'; -import { createUpdateArray } from '../UpdateHandler'; -import { - TYPE_CONTEXT, - TYPE_FORWARD_REF, TYPE_FRAGMENT, - TYPE_LAZY, - TYPE_MEMO, TYPE_PROFILER, - TYPE_PROVIDER, TYPE_STRICT_MODE, - TYPE_SUSPENSE, -} from '../utils/elementType'; -import { VNode } from './VNode'; -import {HorizonElement} from '../Types'; - -const typeLazyMap = { - [TYPE_FORWARD_REF]: ForwardRef, - [TYPE_MEMO]: MemoComponent, -}; -const typeMap = { - ...typeLazyMap, - [TYPE_PROVIDER]: ContextProvider, - [TYPE_CONTEXT]: ContextConsumer, - [TYPE_LAZY]: LazyComponent, -}; - -const newVirtualNode = function(tag: VNodeTag, key?: null | string, vNodeProps?: any, outerDom?: any): VNode { - return new VNode(tag, vNodeProps, key, outerDom); -}; - -function isClassComponent(comp: Function) { - // 如果使用 getPrototypeOf 方法获取构造函数,不能兼容业务组组件继承组件的使用方式,会误认为是函数组件 - // 如果使用静态属性,部分函数高阶组件会将类组件的静态属性复制到自身,导致误判为类组件 - // 既然已经兼容使用了该标识符,那么继续使用 - return comp.prototype?.isReactComponent === true; -} - -// 解析懒组件的tag -export function getLazyVNodeTag(lazyComp: any): string { - let vNodeTag = ClsOrFunComponent; - if (typeof lazyComp === 'function') { - vNodeTag = isClassComponent(lazyComp) ? ClassComponent : FunctionComponent; - } else if (lazyComp !== undefined && lazyComp !== null && typeLazyMap[lazyComp.vtype]) { - vNodeTag = typeLazyMap[lazyComp.vtype]; - } - return vNodeTag; -} - -// 创建processing -export function updateVNode(vNode: VNode, vNodeProps?: any): VNode { - if (vNode.tag === ClassComponent) { - vNode.oldState = vNode.state; - } - - if (vNode.tag === SuspenseComponent) { - vNode.oldSuspenseChildStatus = vNode.suspenseChildStatus; - vNode.oldChildren = vNode.children; - } - - vNode.oldProps = vNode.props; - vNode.props = vNodeProps; - - vNode.oldRef = vNode.ref; - - FlagUtils.setNoFlags(vNode); - vNode.dirtyNodes = []; - vNode.isCreated = false; - - return vNode; -} - -function getVNodeTag(type: any) { - let vNodeTag = ClsOrFunComponent; - let isLazy = false; - - if (typeof type === 'function') { - if (isClassComponent(type)) { - vNodeTag = ClassComponent; - } - } else if (typeof type === 'string') { - vNodeTag = DomComponent; - } else if (type === TYPE_SUSPENSE) { - vNodeTag = SuspenseComponent; - } else if (typeof type === 'object' && type !== null && typeMap[type.vtype]) { - vNodeTag = typeMap[type.vtype]; - isLazy = type.vtype === TYPE_LAZY; - } else { - throw Error(`Component type is invalid, got: ${type == null ? type : typeof type}`); - } - return { vNodeTag, isLazy }; -} - -export function createVNode(tag: VNodeTag | string, ...secondArg) { - let vNode = null; - switch (tag) { - case Fragment: - const [fragmentKey, fragmentProps] = secondArg; - vNode = newVirtualNode(Fragment, fragmentKey, fragmentProps); - vNode.shouldUpdate = true; - break; - case DomText: - const content = secondArg[0]; - vNode = newVirtualNode(DomText, null, content); - vNode.shouldUpdate = true; - break; - case DomPortal: - const portal = secondArg[0]; - const children = portal.children ?? []; - vNode = newVirtualNode(DomPortal, portal.key, children); - vNode.shouldUpdate = true; - vNode.outerDom = portal.outerDom; - break; - case 'props': - const [type, key, props] = secondArg; - - const { vNodeTag, isLazy } = getVNodeTag(type); - - vNode = newVirtualNode(vNodeTag, key, props); - vNode.type = type; - vNode.shouldUpdate = true; - - // lazy类型的特殊处理 - vNode.isLazyComponent = isLazy; - if (isLazy) { - vNode.lazyType = type; - } - break; - case TreeRoot: - // 创建treeRoot - vNode = newVirtualNode(TreeRoot, null, null, secondArg[0]); - vNode.path.push(0); - - createUpdateArray(vNode); - break; - } - - return vNode; -} - -export function updateVNodePath(vNode: VNode) { - vNode.path = [...vNode.parent.path, vNode.cIndex]; -} - -export function createVNodeFromElement(element: HorizonElement): VNode { - const type = element.type; - const key = element.key; - const props = element.props; - - if (type === TYPE_STRICT_MODE || type === TYPE_FRAGMENT || type === TYPE_PROFILER) { - return createVNode(Fragment, key, props.children); - } else { - return createVNode('props', type, key, props); - } -} - -// 直接更新子节点属性即可,不需要diff -export function onlyUpdateChildVNodes(processing: VNode): Array | null { - // 检查子树是否需要更新 - if (processing.childShouldUpdate) { - // 此vNode无需更新,但是子树需要 - if (!processing.isCreated && processing.children && processing.children.length) { - // 更新子节点 - processing.children.forEach(child => { - updateVNode(child, child.props); - updateVNodePath(child); - }); - } - - // 返回子节点,继续遍历 - return processing.children; - } - - // 子树无需工作 - return null; -} - diff --git a/libs/horizon/src/renderer/vnode/VNodeFlags.ts b/libs/horizon/src/renderer/vnode/VNodeFlags.ts deleted file mode 100644 index 7259beb7..00000000 --- a/libs/horizon/src/renderer/vnode/VNodeFlags.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * 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 deleted file mode 100644 index 00ebe158..00000000 --- a/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts +++ /dev/null @@ -1,66 +0,0 @@ -// 从当前节点向上遍历,更新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 deleted file mode 100644 index 2ec1db13..00000000 --- a/libs/horizon/src/renderer/vnode/VNodeTags.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * 定义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 deleted file mode 100644 index 3b921738..00000000 --- a/libs/horizon/src/renderer/vnode/VNodeUtils.ts +++ /dev/null @@ -1,278 +0,0 @@ -/** - * 提供: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; -} - -