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/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 f4c568ea..1ae324be 100644 --- a/libs/horizon/src/renderer/hooks/HookMain.ts +++ b/libs/horizon/src/renderer/hooks/HookMain.ts @@ -20,7 +20,7 @@ import {useReducerImpl} from './UseReducerHook'; 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/ClassComponent.ts b/libs/horizon/src/renderer/render/ClassComponent.ts index 002e343d..f0056f04 100644 --- a/libs/horizon/src/renderer/render/ClassComponent.ts +++ b/libs/horizon/src/renderer/render/ClassComponent.ts @@ -33,25 +33,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); @@ -98,26 +98,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; @@ -137,13 +141,13 @@ export function captureRender(processing: VNode): VNode | null { 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 +166,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); -} diff --git a/libs/horizon/src/renderer/render/FunctionComponent.ts b/libs/horizon/src/renderer/render/FunctionComponent.ts index 8191293b..563cda52 100644 --- a/libs/horizon/src/renderer/render/FunctionComponent.ts +++ b/libs/horizon/src/renderer/render/FunctionComponent.ts @@ -2,7 +2,7 @@ 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'; @@ -26,12 +26,6 @@ function checkIfCanReuseChildren(processing: VNode, shouldUpdate?: boolean) { // 如果props或者context改变了 if (oldProps !== newProps || getContextChangeCtx() || processing.isDepContextChange) { isCanReuse = false; - } else { - if (shouldUpdate && processing.suspenseChildThrow) { - // 使用完后恢复 - processing.suspenseChildThrow = false; - isCanReuse = false; - } } } else { isCanReuse = false; @@ -68,7 +62,7 @@ export function captureFunctionComponent( // 在执行exeFunctionHook前先设置stateChange为false setStateChange(false); - const newElements = exeFunctionHook( + const newElements = runFunctionWithHooks( processing.tag === ForwardRef ? funcComp.render : funcComp, nextProps, processing.tag === ForwardRef ? processing.ref : undefined, 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..225312aa 100644 --- a/libs/horizon/src/renderer/render/SuspenseComponent.ts +++ b/libs/horizon/src/renderer/render/SuspenseComponent.ts @@ -4,8 +4,8 @@ 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'; @@ -47,7 +47,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 +71,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 +80,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 +88,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 { @@ -104,12 +104,12 @@ function updateFallback(processing: VNode): Array | VNode | null { 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; @@ -133,7 +133,7 @@ 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 +178,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 +186,13 @@ export function handleSuspenseChildThrowError(parent: VNode, processing: VNode, } } - if(processing.tag === FunctionComponent) { - processing.isSuspended = true; + 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 +211,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 +220,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 8c98e673..7189db06 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; // 需要改动的节点数组 @@ -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; // 保存要更新的节点 @@ -116,11 +128,13 @@ export class VNode { 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; @@ -143,8 +157,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;