diff --git a/libs/horizon/src/renderer/ContextSaver.ts b/libs/horizon/src/renderer/ContextSaver.ts index ef243cce..6fd6ef9a 100644 --- a/libs/horizon/src/renderer/ContextSaver.ts +++ b/libs/horizon/src/renderer/ContextSaver.ts @@ -1,6 +1,6 @@ /** * 保存与深度遍历相关的一些context。 - * 在深度遍历过程中,begin阶段会修改一些全局的值,在complete阶段会恢复。 + * 在深度遍历过程中,capture阶段会修改一些全局的值,在bubble阶段会恢复。 */ import type { VNode, ContextType } from './Types'; @@ -11,98 +11,50 @@ 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'; -let ctxOldChange = false; let ctxNamespace = ''; -function setContext(vNode: VNode, contextName, value) { - if (vNode.contexts === null) { - vNode.contexts = { - [contextName]: value, - }; - } else { - vNode.contexts[contextName] = value; - } -} - -function getContext(vNode: VNode, contextName) { - if (vNode.contexts !== null) { - return vNode.contexts[contextName]; - } -} - // capture阶段设置 -function setNamespaceCtx(vNode: VNode, dom?: Container) { +export function setNamespaceCtx(vNode: VNode, dom?: Container) { const nextContext = getNSCtx(ctxNamespace, vNode.type, dom); - setContext(vNode, CTX_NAMESPACE, ctxNamespace); + vNode.context = ctxNamespace; + ctxNamespace = nextContext; } // bubble阶段恢复 -function resetNamespaceCtx(vNode: VNode) { - ctxNamespace = getContext(vNode, CTX_NAMESPACE); +export function resetNamespaceCtx(vNode: VNode) { + ctxNamespace = vNode.context; } -function getNamespaceCtx(): string { +export function getNamespaceCtx(): string { return ctxNamespace; } -function setContextCtx(providerVNode: VNode, nextValue: T) { +export function setContext(providerVNode: VNode, nextValue: T) { const context: ContextType = providerVNode.type._context; - setContext(providerVNode, CTX_CONTEXT, context.value); + providerVNode.context = context.value; + context.value = nextValue; } -function resetContextCtx(providerVNode: VNode) { +export function resetContext(providerVNode: VNode) { const context: ContextType = providerVNode.type._context; - context.value = getContext(providerVNode, CTX_CONTEXT); + context.value = providerVNode.context; } // 在局部更新时,恢复父节点的context -function recoverParentsContextCtx(vNode: VNode) { +export function recoverParentContext(vNode: VNode) { let parent = vNode.parent; while (parent !== null) { if (parent.tag === ContextProvider) { - const newValue = parent.props.value; - setContextCtx(parent, newValue); + parent.context = parent.props.value; } parent = parent.parent; } } -function setContextChangeCtx(providerVNode: VNode, didChange: boolean) { - setContext(providerVNode, CTX_OLD_CHANGE, didChange); - ctxOldChange = didChange; -} - -function getContextChangeCtx() { - return ctxOldChange; -} - -function resetContextChangeCtx(vNode: VNode) { - ctxOldChange = getContext(vNode, CTX_OLD_CHANGE); -} - -export { - getNamespaceCtx, - resetNamespaceCtx, - setNamespaceCtx, - setContextCtx, - resetContextCtx, - recoverParentsContextCtx, - setContextChangeCtx, - getContextChangeCtx, - resetContextChangeCtx, -}; - diff --git a/libs/horizon/src/renderer/ErrorHandler.ts b/libs/horizon/src/renderer/ErrorHandler.ts index 837f25c5..8eaed4f8 100644 --- a/libs/horizon/src/renderer/ErrorHandler.ts +++ b/libs/horizon/src/renderer/ErrorHandler.ts @@ -2,7 +2,7 @@ * 异常错误处理 */ -import type {VNode} from './Types'; +import type { PromiseType, VNode } from './Types'; import type {Update} from './UpdateHandler'; import {ClassComponent, TreeRoot} from './vnode/VNodeTags'; @@ -62,7 +62,9 @@ function createClassErrorUpdate( } return update; } - +function isPromise(error: any): error is PromiseType { + return error !== null && typeof error === 'object' && typeof error.then === 'function' +} // 处理capture和bubble阶段抛出的错误 export function handleRenderThrowError( sourceVNode: VNode, @@ -74,7 +76,7 @@ export function handleRenderThrowError( sourceVNode.dirtyNodes = null; // error是个promise - if (error !== null && typeof error === 'object' && typeof error.then === 'function') { + if (isPromise(error)) { // 抛出异常的节点,向上寻找,是否有suspense组件 const foundSuspense = handleSuspenseChildThrowError(sourceVNode.parent, sourceVNode, error); if (foundSuspense) { diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts index 721bbff6..1680c949 100644 --- a/libs/horizon/src/renderer/TreeBuilder.ts +++ b/libs/horizon/src/renderer/TreeBuilder.ts @@ -29,7 +29,7 @@ import { isExecuting, setExecuteMode } from './ExecuteMode'; -import { recoverParentsContextCtx, resetNamespaceCtx, setNamespaceCtx } from './ContextSaver'; +import { recoverParentContext, resetNamespaceCtx, setNamespaceCtx } from './ContextSaver'; import { updateChildShouldUpdate, updateParentsChildShouldUpdate, @@ -231,7 +231,7 @@ function buildVNodeTree(treeRoot: VNode) { } // 恢复父节点的context - recoverParentsContextCtx(startVNode); + recoverParentContext(startVNode); } // 重置环境变量,为重新进行深度遍历做准备 diff --git a/libs/horizon/src/renderer/Types.ts b/libs/horizon/src/renderer/Types.ts index f95c4720..6e1572fe 100644 --- a/libs/horizon/src/renderer/Types.ts +++ b/libs/horizon/src/renderer/Types.ts @@ -54,3 +54,10 @@ export interface PromiseType { ): void | PromiseType; } +export interface SuspenseState { + promiseSet: Set> | null; // suspense组件的promise列表 + childStatus: string; + oldChildStatus: string; // 上一次Suspense的Children是否显示 + didCapture: boolean; // suspense是否捕获了异常 + promiseResolved: boolean; // suspense的promise是否resolve +} diff --git a/libs/horizon/src/renderer/hooks/HookMain.ts b/libs/horizon/src/renderer/hooks/HookMain.ts index 675afdbb..30cf0e11 100644 --- a/libs/horizon/src/renderer/hooks/HookMain.ts +++ b/libs/horizon/src/renderer/hooks/HookMain.ts @@ -9,7 +9,7 @@ import { import {HookStage, setHookStage} from './HookStage'; // hook对外入口 -export function exeFunctionHook, Arg>( +export function runFunctionWithHooks, Arg>( funcComp: (props: Props, arg: Arg) => any, props: Props, arg: Arg, diff --git a/libs/horizon/src/renderer/render/BaseComponent.ts b/libs/horizon/src/renderer/render/BaseComponent.ts index 94e5ecf4..73749909 100644 --- a/libs/horizon/src/renderer/render/BaseComponent.ts +++ b/libs/horizon/src/renderer/render/BaseComponent.ts @@ -7,7 +7,7 @@ import { TreeRoot, SuspenseComponent, } from '../vnode/VNodeTags'; -import { getContextChangeCtx, setContextCtx, setNamespaceCtx } from '../ContextSaver'; +import { setContext, setNamespaceCtx } from '../ContextSaver'; import { FlagUtils } from '../vnode/VNodeFlags'; import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator'; import componentRenders from './index'; @@ -26,7 +26,7 @@ function handlerContext(processing: VNode) { break; case ContextProvider: { const newValue = processing.props.value; - setContextCtx(processing, newValue); + setContext(processing, newValue); break; } // No Default @@ -41,7 +41,6 @@ export function captureVNode(processing: VNode): VNode | null { if ( !processing.isCreated && processing.oldProps === processing.props && - !getContextChangeCtx() && !processing.shouldUpdate ) { // 复用还需对stack进行处理 diff --git a/libs/horizon/src/renderer/render/ClassComponent.ts b/libs/horizon/src/renderer/render/ClassComponent.ts index 002e343d..2856ecf9 100644 --- a/libs/horizon/src/renderer/render/ClassComponent.ts +++ b/libs/horizon/src/renderer/render/ClassComponent.ts @@ -18,7 +18,6 @@ import { markRef } from './BaseComponent'; import { processUpdates, } from '../UpdateHandler'; -import { getContextChangeCtx } from '../ContextSaver'; import { setProcessingClassVNode } from '../GlobalVar'; import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator'; import { createChildrenByDiff } from '../diff/nodeDiffComparator'; @@ -33,25 +32,25 @@ export function getCurrentContext(clazz, processing: VNode) { } // 挂载实例 -function mountInstance(clazz, processing: VNode, nextProps: object) { +function mountInstance(ctor, processing: VNode, nextProps: object) { if (!processing.isCreated) { processing.isCreated = true; FlagUtils.markAddition(processing); } // 构造实例 - const inst = callConstructor(processing, clazz, nextProps); + const inst = callConstructor(processing, ctor, nextProps); inst.props = nextProps; inst.state = processing.state; - inst.context = getCurrentContext(clazz, processing); + inst.context = getCurrentContext(ctor, processing); inst.refs = {}; processUpdates(processing, inst, nextProps); inst.state = processing.state; // 在调用类组建的渲染方法之前调用 并且在初始挂载及后续更新时都会被调用 - callDerivedStateFromProps(processing, clazz.getDerivedStateFromProps, nextProps); + callDerivedStateFromProps(processing, ctor.getDerivedStateFromProps, nextProps); callComponentWillMount(processing, inst, nextProps); markComponentDidMount(processing); @@ -87,7 +86,7 @@ function callUpdateLifeCycle(processing: VNode, nextProps: object, clazz) { } } -function markLifeCycle(processing: VNode, nextProps: object, shouldUpdate: Boolean) { +function markLifeCycle(processing: VNode, nextProps: object, shouldUpdate: boolean) { if (processing.isCreated) { markComponentDidMount(processing); } else if (processing.state !== processing.oldState || shouldUpdate) { @@ -98,26 +97,30 @@ function markLifeCycle(processing: VNode, nextProps: object, shouldUpdate: Boole // 用于类组件 export function captureRender(processing: VNode): VNode | null { - let clazz = processing.type; + const ctor = processing.type; let nextProps = processing.props; if (processing.isLazyComponent) { - nextProps = mergeDefaultProps(clazz, nextProps); - if (processing.promiseResolve) { // 该函数被 lazy 组件使用,未加载的组件需要加载组件的真实内容 - clazz = clazz._load(clazz._content); - } + nextProps = mergeDefaultProps(ctor, nextProps); } resetDepContexts(processing); + // suspense打断后,再次render只需初次渲染 + if (processing.isSuspended) { + mountInstance(ctor, processing, nextProps); + processing.isSuspended = false; + return createChildren(ctor, processing); + } + // 通过 shouldUpdate 判断是否要复用 children,该值和props,state,context的变化,shouldComponentUpdate,forceUpdate api的调用结果有关 let shouldUpdate; const inst = processing.realNode; if (inst === null) { // 挂载新组件,一定会更新 - mountInstance(clazz, processing, nextProps); + mountInstance(ctor, processing, nextProps); shouldUpdate = true; } else { // 更新 - const newContext = getCurrentContext(clazz, processing); + const newContext = getCurrentContext(ctor, processing); // 子节点抛出异常时,如果本class是个捕获异常的处理节点,这时候oldProps是null,所以需要使用props const oldProps = (processing.flags & DidCapture) === DidCapture ? processing.props : processing.oldProps; @@ -132,18 +135,17 @@ export function captureRender(processing: VNode): VNode | null { // 如果 props, state, context 都没有变化且 isForceUpdate 为 false则不需要更新 shouldUpdate = oldProps !== processing.props || inst.state !== processing.state || - getContextChangeCtx() || processing.isForceUpdate; if (shouldUpdate) { // derivedStateFromProps会修改nextState,因此需要调用 - callDerivedStateFromProps(processing, clazz.getDerivedStateFromProps, nextProps); + callDerivedStateFromProps(processing, ctor.getDerivedStateFromProps, nextProps); if (!processing.isForceUpdate) { // 业务可以通过 shouldComponentUpdate 函数进行优化阻止更新 shouldUpdate = callShouldComponentUpdate(processing, oldProps, nextProps, processing.state, newContext); } if (shouldUpdate) { - callUpdateLifeCycle(processing, nextProps, clazz); + callUpdateLifeCycle(processing, nextProps, ctor); } inst.state = processing.state; inst.context = newContext; @@ -162,16 +164,10 @@ export function captureRender(processing: VNode): VNode | null { // 不复用 if (shouldUpdate) { - return createChildren(clazz, processing); + return createChildren(ctor, processing); } else { return onlyUpdateChildVNodes(processing); } } -export function bubbleRender(processing: VNode) {} - -// 用于未完成的类组件 -export function getIncompleteClassComponent(clazz, processing: VNode, nextProps: object): VNode | null { - mountInstance(clazz, processing, nextProps); - return createChildren(clazz, processing); -} +export function bubbleRender() {} diff --git a/libs/horizon/src/renderer/render/ContextProvider.ts b/libs/horizon/src/renderer/render/ContextProvider.ts index 24aaa9f4..42058bb8 100644 --- a/libs/horizon/src/renderer/render/ContextProvider.ts +++ b/libs/horizon/src/renderer/render/ContextProvider.ts @@ -4,9 +4,8 @@ import { isSame } from '../utils/compare'; import { ClassComponent, ContextProvider } from '../vnode/VNodeTags'; import { pushForceUpdate } from '../UpdateHandler'; import { - getContextChangeCtx, - resetContextCtx, - setContextCtx, + resetContext, + setContext, } from '../ContextSaver'; import { travelVNodeTree } from '../vnode/VNodeUtils'; import { launchUpdateFromVNode } from '../TreeBuilder'; @@ -75,14 +74,14 @@ function captureContextProvider(processing: VNode): VNode | null { const newCtx = newProps.value; // 更新processing的context值为newProps.value - setContextCtx(processing, newCtx); + setContext(processing, newCtx); if (oldProps !== null) { const oldCtx = oldProps.value; const isSameContext = isSame(oldCtx, newCtx); if (isSameContext) { // context没有改变,复用 - if (oldProps.children === newProps.children && !getContextChangeCtx()) { + if (oldProps.children === newProps.children) { return onlyUpdateChildVNodes(processing); } } else { @@ -101,6 +100,6 @@ export function captureRender(processing: VNode): VNode | null { } export function bubbleRender(processing: VNode) { - resetContextCtx(processing); + resetContext(processing); } diff --git a/libs/horizon/src/renderer/render/ForwardRef.ts b/libs/horizon/src/renderer/render/ForwardRef.ts index 35680a3f..ead06587 100644 --- a/libs/horizon/src/renderer/render/ForwardRef.ts +++ b/libs/horizon/src/renderer/render/ForwardRef.ts @@ -1,8 +1,8 @@ import type {VNode} from '../Types'; import {captureRender as funCaptureRender} from './FunctionComponent'; -export function captureRender(processing: VNode, shouldUpdate?: boolean): VNode | null { - return funCaptureRender(processing, shouldUpdate); +export function captureRender(processing: VNode): VNode | null { + return funCaptureRender(processing); } export function bubbleRender() {} diff --git a/libs/horizon/src/renderer/render/FunctionComponent.ts b/libs/horizon/src/renderer/render/FunctionComponent.ts index d7397e32..8ce11aed 100644 --- a/libs/horizon/src/renderer/render/FunctionComponent.ts +++ b/libs/horizon/src/renderer/render/FunctionComponent.ts @@ -2,10 +2,9 @@ import type { VNode } from '../Types'; import { mergeDefaultProps } from './LazyComponent'; import { resetDepContexts } from '../components/context/Context'; -import { exeFunctionHook } from '../hooks/HookMain'; +import { runFunctionWithHooks } from '../hooks/HookMain'; import { ForwardRef } from '../vnode/VNodeTags'; import { FlagUtils, Update } from '../vnode/VNodeFlags'; -import { getContextChangeCtx } from '../ContextSaver'; import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator'; import { createChildrenByDiff } from '../diff/nodeDiffComparator'; @@ -16,28 +15,10 @@ export function bubbleRender() { } // 判断children是否可以复用 -function checkIfCanReuseChildren(processing: VNode, shouldUpdate?: boolean) { - let isCanReuse = true; - - if (!processing.isCreated) { - const oldProps = processing.oldProps; - const newProps = processing.props; - - // 如果props或者context改变了 - if (oldProps !== newProps || getContextChangeCtx() || processing.isDepContextChange) { - isCanReuse = false; - } else { - if (shouldUpdate && processing.suspenseChildThrow) { - // 使用完后恢复 - processing.suspenseChildThrow = false; - isCanReuse = false; - } - } - } else { - isCanReuse = false; - } - - return isCanReuse; +function checkIfCanReuseChildren(processing: VNode) { + return !processing.isCreated && + processing.oldProps === processing.props && + !processing.isDepContextChange; } export function setStateChange(isUpdate) { @@ -52,7 +33,6 @@ export function captureFunctionComponent( processing: VNode, funcComp: any, nextProps: any, - shouldUpdate?: boolean, ) { // 函数组件内已完成异步动作 if (processing.isSuspended) { @@ -64,11 +44,11 @@ export function captureFunctionComponent( } resetDepContexts(processing); - const isCanReuse = checkIfCanReuseChildren(processing, shouldUpdate); + const isCanReuse = checkIfCanReuseChildren(processing); // 在执行exeFunctionHook前先设置stateChange为false setStateChange(false); - const newElements = exeFunctionHook( + const newElements = runFunctionWithHooks( processing.tag === ForwardRef ? funcComp.render : funcComp, nextProps, processing.tag === ForwardRef ? processing.ref : undefined, @@ -88,7 +68,7 @@ export function captureFunctionComponent( return processing.child; } -export function captureRender(processing: VNode, shouldUpdate?: boolean): VNode | null { +export function captureRender(processing: VNode): VNode | null { const Component = processing.type; const unresolvedProps = processing.props; const resolvedProps = @@ -100,7 +80,6 @@ export function captureRender(processing: VNode, shouldUpdate?: boolean): VNode processing, Component, resolvedProps, - shouldUpdate, ); } diff --git a/libs/horizon/src/renderer/render/IncompleteClassComponent.ts b/libs/horizon/src/renderer/render/IncompleteClassComponent.ts deleted file mode 100644 index 8e02df51..00000000 --- a/libs/horizon/src/renderer/render/IncompleteClassComponent.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type {VNode} from '../Types'; - -import {mergeDefaultProps} from './LazyComponent'; -import {ClassComponent} from '../vnode/VNodeTags'; -import {resetDepContexts} from '../components/context/Context'; -import {getIncompleteClassComponent} from './ClassComponent'; - -function captureIncompleteClassComponent(processing, Component, nextProps) { - processing.tag = ClassComponent; - - resetDepContexts(processing); - - return getIncompleteClassComponent(Component, processing, nextProps); -} - -export function captureRender(processing: VNode): VNode | null { - const Component = processing.type; - const unresolvedProps = processing.props; - const resolvedProps = - processing.isLazyComponent - ? mergeDefaultProps(Component, unresolvedProps) - : unresolvedProps; - - return captureIncompleteClassComponent(processing, Component, resolvedProps); -} - -export function bubbleRender(processing: VNode) {} diff --git a/libs/horizon/src/renderer/render/SuspenseComponent.ts b/libs/horizon/src/renderer/render/SuspenseComponent.ts index 0686dc3f..b57bef99 100644 --- a/libs/horizon/src/renderer/render/SuspenseComponent.ts +++ b/libs/horizon/src/renderer/render/SuspenseComponent.ts @@ -1,17 +1,16 @@ -import type {VNode, PromiseType} from '../Types'; +import type { VNode, PromiseType } from '../Types'; -import {FlagUtils, Interrupted} from '../vnode/VNodeFlags'; -import {onlyUpdateChildVNodes, updateVNode, createFragmentVNode} from '../vnode/VNodeCreator'; +import { FlagUtils, Interrupted } from '../vnode/VNodeFlags'; +import { onlyUpdateChildVNodes, updateVNode, createFragmentVNode } from '../vnode/VNodeCreator'; import { ClassComponent, + ForwardRef, FunctionComponent, - IncompleteClassComponent, SuspenseComponent, } from '../vnode/VNodeTags'; -import {pushForceUpdate} from '../UpdateHandler'; -import {launchUpdateFromVNode, tryRenderFromRoot} from '../TreeBuilder'; -import {updateShouldUpdateOfTree} from '../vnode/VNodeShouldUpdate'; -import {getContextChangeCtx} from '../ContextSaver'; +import { pushForceUpdate } from '../UpdateHandler'; +import { launchUpdateFromVNode, tryRenderFromRoot } from '../TreeBuilder'; +import { updateShouldUpdateOfTree } from '../vnode/VNodeShouldUpdate'; import { markVNodePath } from '../utils/vNodePath'; export enum SuspenseChildStatus { @@ -47,7 +46,7 @@ function createFallback(processing: VNode, fallbackChildren) { fallbackFragment.eIndex = 1; fallbackFragment.cIndex = 1; markVNodePath(fallbackFragment); - processing.suspenseChildStatus = SuspenseChildStatus.ShowFallback; + processing.suspenseState.childStatus = SuspenseChildStatus.ShowFallback; return fallbackFragment; } @@ -71,7 +70,7 @@ function createSuspenseChildren(processing: VNode, newChildren) { processing.dirtyNodes = [oldFallbackFragment]; } // SuspenseComponent 中使用 - processing.suspenseChildStatus = SuspenseChildStatus.ShowChild; + processing.suspenseState.childStatus = SuspenseChildStatus.ShowChild; } else { childFragment = createFragmentVNode(null, newChildren); } @@ -80,7 +79,7 @@ function createSuspenseChildren(processing: VNode, newChildren) { childFragment.cIndex = 0; markVNodePath(childFragment); processing.child = childFragment; - processing.promiseResolve = false; + processing.suspenseState.promiseResolved = false; return processing.child; } @@ -88,10 +87,10 @@ export function captureSuspenseComponent(processing: VNode) { const nextProps = processing.props; // suspense被捕获后需要展示fallback - const showFallback = processing.suspenseDidCapture; + const showFallback = processing.suspenseState.didCapture; if (showFallback) { - processing.suspenseDidCapture = false; + processing.suspenseState.didCapture = false; const nextFallbackChildren = nextProps.fallback; return createFallback(processing, nextFallbackChildren); } else { @@ -101,15 +100,15 @@ export function captureSuspenseComponent(processing: VNode) { } function updateFallback(processing: VNode): Array | VNode | null { - const childFragment: VNode | null= processing.child; + const childFragment: VNode | null = processing.child; if (childFragment?.childShouldUpdate) { - if (processing.promiseResolve) { + if (processing.suspenseState.promiseResolved) { // promise已完成,展示promise返回的新节点 return captureSuspenseComponent(processing); } else { // promise未完成,继续显示fallback,不需要继续刷新子节点 - const fallbackFragment: VNode = processing.child.next; + const fallbackFragment: VNode = processing.child!.next!; childFragment.childShouldUpdate = false; fallbackFragment.childShouldUpdate = false; return null; @@ -130,10 +129,9 @@ export function captureRender(processing: VNode, shouldUpdate: boolean): Array): boolean { let vNode = parent; // 向上找到最近的不在fallback状态的Suspense,并触发重新渲染 do { if (vNode.tag === SuspenseComponent && canCapturePromise(vNode)) { - if (vNode.suspensePromises === null) { - vNode.suspensePromises = new Set(); + if (vNode.suspenseState.promiseSet === null) { + vNode.suspenseState.promiseSet = new Set(); } - vNode.suspensePromises.add(error); + vNode.suspenseState.promiseSet.add(promise); - processing.suspenseChildThrow = true; // 移除生命周期flag 和 中断flag FlagUtils.removeLifecycleEffectFlags(processing); @@ -178,7 +176,7 @@ export function handleSuspenseChildThrowError(parent: VNode, processing: VNode, if (processing.tag === ClassComponent) { if (processing.isCreated) { // 渲染类组件场景,要标志未完成(否则会触发componentWillUnmount) - processing.tag = IncompleteClassComponent; + processing.isSuspended = true; } else { // 类组件更新,标记强制更新,否则被memo等优化跳过 pushForceUpdate(processing); @@ -186,13 +184,13 @@ export function handleSuspenseChildThrowError(parent: VNode, processing: VNode, } } - if(processing.tag === FunctionComponent) { + if (processing.tag === FunctionComponent || processing.tag === ForwardRef) { processing.isSuspended = true; } // 应该抛出promise未完成更新,标志待更新 processing.shouldUpdate = true; - vNode.suspenseDidCapture = true; + vNode.suspenseState.didCapture = true; launchUpdateFromVNode(vNode); return true; @@ -211,7 +209,7 @@ function resolvePromise(suspenseVNode: VNode, promise: PromiseType) { if (promiseCache !== null) { promiseCache.delete(promise); } - suspenseVNode.promiseResolve = true; + suspenseVNode.suspenseState.promiseResolved = true; const root = updateShouldUpdateOfTree(suspenseVNode); if (root !== null) { tryRenderFromRoot(root); @@ -220,9 +218,9 @@ function resolvePromise(suspenseVNode: VNode, promise: PromiseType) { // 对于每个promise,添加一个侦听器,以便当它resolve时,重新渲染 export function listenToPromise(suspenseVNode: VNode) { - const promises: Set> | null = suspenseVNode.suspensePromises; + const promises: Set> | null = suspenseVNode.suspenseState.promiseSet; if (promises !== null) { - suspenseVNode.suspensePromises = null; + suspenseVNode.suspenseState.promiseSet = null; // 记录已经监听的 promise let promiseCache = suspenseVNode.realNode; diff --git a/libs/horizon/src/renderer/render/index.ts b/libs/horizon/src/renderer/render/index.ts index 4497627a..4e300c93 100644 --- a/libs/horizon/src/renderer/render/index.ts +++ b/libs/horizon/src/renderer/render/index.ts @@ -9,7 +9,6 @@ import * as DomComponentRender from './DomComponent'; import * as DomPortalRender from './DomPortal'; import * as TreeRootRender from './TreeRoot'; import * as DomTextRender from './DomText'; -import * as IncompleteClassComponentRender from './IncompleteClassComponent'; import * as LazyComponentRender from './LazyComponent'; import * as MemoComponentRender from './MemoComponent'; import * as SuspenseComponentRender from './SuspenseComponent'; @@ -25,15 +24,14 @@ import { DomPortal, TreeRoot, DomText, - IncompleteClassComponent, LazyComponent, MemoComponent, SuspenseComponent, } from '../vnode/VNodeTags'; export { - BaseComponentRender -} + BaseComponentRender, +}; export default { [ClassComponent]: ClassComponentRender, @@ -46,8 +44,7 @@ export default { [DomPortal]: DomPortalRender, [TreeRoot]: TreeRootRender, [DomText]: DomTextRender, - [IncompleteClassComponent]: IncompleteClassComponentRender, [LazyComponent]: LazyComponentRender, [MemoComponent]: MemoComponentRender, [SuspenseComponent]: SuspenseComponentRender, -} +}; diff --git a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts index adb21135..7201613d 100644 --- a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts +++ b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts @@ -32,7 +32,7 @@ import { callEffectRemove, callUseEffects, callUseLayoutEffectCreate, - callUseLayoutEffectRemove + callUseLayoutEffectRemove, } from './HookEffectHandler'; import { handleSubmitError } from '../ErrorHandler'; import { @@ -192,7 +192,8 @@ function unmountVNode(vNode: VNode): void { const instance = vNode.realNode; // 当constructor中抛出异常时,instance会是null,这里判断一下instance是否为空 - if (instance && typeof instance.componentWillUnmount === 'function') { + // suspense打断时不需要触发WillUnmount + if (instance && typeof instance.componentWillUnmount === 'function' && !vNode.isSuspended) { callComponentWillUnmount(vNode, instance); } break; @@ -212,11 +213,11 @@ function unmountVNode(vNode: VNode): void { // 卸载vNode,递归遍历子vNode function unmountNestedVNodes(vNode: VNode): void { travelVNodeTree(vNode, (node) => { - unmountVNode(node); - }, node => - // 如果是DomPortal,不需要遍历child - node.tag === DomPortal - , vNode, null); + unmountVNode(node); + }, node => + // 如果是DomPortal,不需要遍历child + node.tag === DomPortal + , vNode, null); } function submitAddition(vNode: VNode): void { @@ -329,7 +330,7 @@ function submitClear(vNode: VNode): void { // 但考虑到用户可能自定义其他属性,所以采用遍历赋值的方式 const customizeKeys = Object.keys(realNode); const keyLength = customizeKeys.length; - for(let i = 0; i < keyLength; i++) { + for (let i = 0; i < keyLength; i++) { const key = customizeKeys[i]; // 测试代码 mock 实例的全部可遍历属性都会被Object.keys方法读取到 // children 属性被复制意味着复制了子节点,因此要排除 @@ -351,7 +352,7 @@ function submitClear(vNode: VNode): void { } let clearChild = vNode.clearChild as VNode; // 上次渲染的child保存在clearChild属性中 // 卸载 clearChild 和 它的兄弟节点 - while(clearChild) { + while (clearChild) { // 卸载子vNode,递归遍历子vNode unmountNestedVNodes(clearChild); clearVNode(clearChild); @@ -399,9 +400,9 @@ function submitUpdate(vNode: VNode): void { } function submitSuspenseComponent(vNode: VNode) { - const suspenseChildStatus = vNode.suspenseChildStatus; - if (suspenseChildStatus !== SuspenseChildStatus.Init) { - hideOrUnhideAllChildren(vNode.child, suspenseChildStatus === SuspenseChildStatus.ShowFallback); + const { childStatus } = vNode.suspenseState; + if (childStatus !== SuspenseChildStatus.Init) { + hideOrUnhideAllChildren(vNode.child, childStatus === SuspenseChildStatus.ShowFallback); } } diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 1bd90f75..29fb008b 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -1,9 +1,24 @@ /** * 虚拟DOM结构体 */ -import { TreeRoot, FunctionComponent, ClassComponent, DomPortal, DomText, ContextConsumer, ForwardRef, SuspenseComponent, LazyComponent, DomComponent, Fragment, ContextProvider, Profiler, MemoComponent, IncompleteClassComponent } from './VNodeTags'; +import { + TreeRoot, + FunctionComponent, + ClassComponent, + DomPortal, + DomText, + ContextConsumer, + ForwardRef, + SuspenseComponent, + LazyComponent, + DomComponent, + Fragment, + ContextProvider, + Profiler, + MemoComponent, +} from './VNodeTags'; import type { VNodeTag } from './VNodeTags'; -import type { RefType, ContextType } from '../Types'; +import type { RefType, ContextType, SuspenseState } from '../Types'; import type { Hook } from '../hooks/HookType'; import { InitFlag } from './VNodeFlags'; @@ -24,7 +39,6 @@ export class VNode { ref: RefType | ((handle: any) => void) | null = null; // 包裹一个函数,submit阶段使用,比如将外部useRef生成的对象赋值到ref上 oldProps: any = null; - suspensePromises: any; // suspense组件的promise列表 changeList: any; // DOM的变更列表 effectList: any[] | null; // useEffect 的更新数组 updates: any[] | null; // TreeRoot和ClassComponent使用的更新数组 @@ -33,7 +47,6 @@ export class VNode { isSuspended = false; // 是否被suspense打断更新 state: any; // ClassComponent和TreeRoot的状态 hooks: Array> | null; // 保存hook - suspenseChildStatus = ''; // Suspense的Children是否显示 depContexts: Array> | null; // FunctionComponent和ClassComponent对context的依赖列表 isDepContextChange: boolean; // context是否变更 dirtyNodes: Array | null = null; // 需要改动的节点数组 @@ -42,7 +55,7 @@ export class VNode { task: any; // 使用这个变量来记录修改前的值,用于恢复。 - contexts: any; + context: any; // 因为LazyComponent会修改tag和type属性,为了能识别,增加一个属性 isLazyComponent: boolean; @@ -55,12 +68,11 @@ export class VNode { oldHooks: Array> | null; // 保存上一次执行的hook oldState: any; oldRef: RefType | ((handle: any) => void) | null = null; - suspenseChildThrow: boolean; - oldSuspenseChildStatus: string; // 上一次Suspense的Children是否显示 oldChild: VNode | null = null; - suspenseDidCapture: boolean; // suspense是否捕获了异常 promiseResolve: boolean; // suspense的promise是否resolve + suspenseState: SuspenseState; + path = ''; // 保存从根到本节点的路径 toUpdateNodes: Set | null; // 保存要更新的节点 @@ -85,7 +97,7 @@ export class VNode { this.stateCallbacks = null; this.state = null; this.oldState = null; - this.contexts = null; + this.context = null; break; case FunctionComponent: this.realNode = null; @@ -106,30 +118,32 @@ export class VNode { this.depContexts = null; this.isDepContextChange = false; this.oldState = null; - this.contexts = null; + this.context = null; break; case DomPortal: this.realNode = null; - this.contexts = null; + this.context = null; break; case DomComponent: this.realNode = null; this.changeList = null; - this.contexts = null; + this.context = null; break; case DomText: this.realNode = null; break; case SuspenseComponent: this.realNode = null; - this.suspensePromises = null; - this.suspenseChildThrow = false; - this.suspenseDidCapture = false; - this.promiseResolve = false; - this.oldSuspenseChildStatus = ''; + this.suspenseState = { + promiseSet: null, + didCapture: false, + promiseResolved: false, + oldChildStatus: '', + childStatus: '' + }; break; case ContextProvider: - this.contexts = null; + this.context = null; break; case MemoComponent: this.effectList = null; @@ -149,8 +163,6 @@ export class VNode { break; case Profiler: break; - case IncompleteClassComponent: - break; } } } diff --git a/libs/horizon/src/renderer/vnode/VNodeCreator.ts b/libs/horizon/src/renderer/vnode/VNodeCreator.ts index 9a677f53..932c7fa7 100644 --- a/libs/horizon/src/renderer/vnode/VNodeCreator.ts +++ b/libs/horizon/src/renderer/vnode/VNodeCreator.ts @@ -38,9 +38,9 @@ const typeMap = { [TYPE_LAZY]: LazyComponent, }; -const newVirtualNode = function(tag: VNodeTag, key?: null | string, vNodeProps?: any, realNode?: any): VNode { +function newVirtualNode (tag: VNodeTag, key?: null | string, vNodeProps?: any, realNode?: any): VNode { return new VNode(tag, vNodeProps, key, realNode); -}; +} function isClassComponent(comp: Function) { // 如果使用 getPrototypeOf 方法获取构造函数,不能兼容业务组组件继承组件的使用方式,会误认为是函数组件 @@ -66,7 +66,7 @@ export function updateVNode(vNode: VNode, vNodeProps?: any): VNode { } if (vNode.tag === SuspenseComponent) { - vNode.oldSuspenseChildStatus = vNode.suspenseChildStatus; + vNode.suspenseState.oldChildStatus = vNode.suspenseState.childStatus; vNode.oldChild = vNode.child; } diff --git a/libs/horizon/src/renderer/vnode/VNodeUtils.ts b/libs/horizon/src/renderer/vnode/VNodeUtils.ts index acda0b39..2875a125 100644 --- a/libs/horizon/src/renderer/vnode/VNodeUtils.ts +++ b/libs/horizon/src/renderer/vnode/VNodeUtils.ts @@ -82,7 +82,7 @@ export function clearVNode(vNode: VNode) { vNode.hooks = null; vNode.props = null; vNode.parent = null; - vNode.suspensePromises = null; + vNode.suspenseState = null; vNode.changeList = null; vNode.effectList = null; vNode.updates = null;