From a332ba1290797f70d43f25975fc2abc2f0310cbe Mon Sep 17 00:00:00 2001 From: * <8> Date: Wed, 30 Mar 2022 16:00:54 +0800 Subject: [PATCH 1/9] Match-id-8baa3f8af8658ae0f6c81042846dfb64857a7df1 --- libs/horizon/src/renderer/ErrorHandler.ts | 8 ++-- libs/horizon/src/renderer/Types.ts | 7 +++ libs/horizon/src/renderer/hooks/HookMain.ts | 2 +- .../src/renderer/render/ClassComponent.ts | 25 +++++------ .../src/renderer/render/FunctionComponent.ts | 10 +---- .../src/renderer/render/SuspenseComponent.ts | 45 ++++++++++--------- libs/horizon/src/renderer/render/index.ts | 6 +-- .../src/renderer/submit/LifeCycleHandler.ts | 6 +-- libs/horizon/src/renderer/vnode/VNode.ts | 39 +++++++++++----- .../src/renderer/vnode/VNodeCreator.ts | 6 +-- libs/horizon/src/renderer/vnode/VNodeUtils.ts | 3 +- 11 files changed, 87 insertions(+), 70 deletions(-) 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..228ae62a 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,13 +98,10 @@ 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); @@ -114,10 +111,10 @@ export function captureRender(processing: VNode): VNode | null { 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 +134,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,7 +159,7 @@ export function captureRender(processing: VNode): VNode | null { // 不复用 if (shouldUpdate) { - return createChildren(clazz, processing); + return createChildren(ctor, processing); } else { return onlyUpdateChildVNodes(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/SuspenseComponent.ts b/libs/horizon/src/renderer/render/SuspenseComponent.ts index 0686dc3f..38a84fbc 100644 --- a/libs/horizon/src/renderer/render/SuspenseComponent.ts +++ b/libs/horizon/src/renderer/render/SuspenseComponent.ts @@ -4,6 +4,7 @@ import {FlagUtils, Interrupted} from '../vnode/VNodeFlags'; import {onlyUpdateChildVNodes, updateVNode, createFragmentVNode} from '../vnode/VNodeCreator'; import { ClassComponent, + ForwardRef, FunctionComponent, IncompleteClassComponent, SuspenseComponent, @@ -47,7 +48,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 +72,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 +81,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 +89,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 +105,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 +134,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); @@ -186,13 +187,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 +212,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 +221,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..2b6f418d 100644 --- a/libs/horizon/src/renderer/render/index.ts +++ b/libs/horizon/src/renderer/render/index.ts @@ -32,8 +32,8 @@ import { } from '../vnode/VNodeTags'; export { - BaseComponentRender -} + BaseComponentRender, +}; export default { [ClassComponent]: ClassComponentRender, @@ -50,4 +50,4 @@ export default { [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..f4aaaddf 100644 --- a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts +++ b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts @@ -399,9 +399,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..166c8c61 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -1,9 +1,25 @@ /** * 虚拟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, + IncompleteClassComponent, +} 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 +40,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 +48,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 +69,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 +129,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; 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..d064b38c 100644 --- a/libs/horizon/src/renderer/vnode/VNodeUtils.ts +++ b/libs/horizon/src/renderer/vnode/VNodeUtils.ts @@ -82,7 +82,8 @@ export function clearVNode(vNode: VNode) { vNode.hooks = null; vNode.props = null; vNode.parent = null; - vNode.suspensePromises = null; + // mark + vNode.suspenseState = null; vNode.changeList = null; vNode.effectList = null; vNode.updates = null; From da66b70642f17b759de01e2e34ce6e3a604db947 Mon Sep 17 00:00:00 2001 From: * <8> Date: Wed, 30 Mar 2022 17:37:46 +0800 Subject: [PATCH 2/9] Match-id-1e7778df481951bd1082406e28f7713d3ca5e6b6 --- .../src/renderer/render/ClassComponent.ts | 13 ++++----- .../render/IncompleteClassComponent.ts | 27 ------------------- .../src/renderer/render/SuspenseComponent.ts | 3 +-- libs/horizon/src/renderer/render/index.ts | 3 --- .../src/renderer/submit/LifeCycleHandler.ts | 19 ++++++------- libs/horizon/src/renderer/vnode/VNode.ts | 3 --- libs/horizon/src/renderer/vnode/VNodeUtils.ts | 1 - 7 files changed, 18 insertions(+), 51 deletions(-) delete mode 100644 libs/horizon/src/renderer/render/IncompleteClassComponent.ts diff --git a/libs/horizon/src/renderer/render/ClassComponent.ts b/libs/horizon/src/renderer/render/ClassComponent.ts index 228ae62a..f0056f04 100644 --- a/libs/horizon/src/renderer/render/ClassComponent.ts +++ b/libs/horizon/src/renderer/render/ClassComponent.ts @@ -106,6 +106,13 @@ export function captureRender(processing: VNode): VNode | null { 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; @@ -166,9 +173,3 @@ export function captureRender(processing: VNode): VNode | null { } 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/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 38a84fbc..225312aa 100644 --- a/libs/horizon/src/renderer/render/SuspenseComponent.ts +++ b/libs/horizon/src/renderer/render/SuspenseComponent.ts @@ -6,7 +6,6 @@ import { ClassComponent, ForwardRef, FunctionComponent, - IncompleteClassComponent, SuspenseComponent, } from '../vnode/VNodeTags'; import {pushForceUpdate} from '../UpdateHandler'; @@ -179,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); diff --git a/libs/horizon/src/renderer/render/index.ts b/libs/horizon/src/renderer/render/index.ts index 2b6f418d..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,7 +24,6 @@ import { DomPortal, TreeRoot, DomText, - IncompleteClassComponent, LazyComponent, MemoComponent, SuspenseComponent, @@ -46,7 +44,6 @@ 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 f4aaaddf..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); diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 166c8c61..7189db06 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -16,7 +16,6 @@ import { ContextProvider, Profiler, MemoComponent, - IncompleteClassComponent, } from './VNodeTags'; import type { VNodeTag } from './VNodeTags'; import type { RefType, ContextType, SuspenseState } from '../Types'; @@ -158,8 +157,6 @@ export class VNode { break; case Profiler: break; - case IncompleteClassComponent: - break; } } } diff --git a/libs/horizon/src/renderer/vnode/VNodeUtils.ts b/libs/horizon/src/renderer/vnode/VNodeUtils.ts index d064b38c..2875a125 100644 --- a/libs/horizon/src/renderer/vnode/VNodeUtils.ts +++ b/libs/horizon/src/renderer/vnode/VNodeUtils.ts @@ -82,7 +82,6 @@ export function clearVNode(vNode: VNode) { vNode.hooks = null; vNode.props = null; vNode.parent = null; - // mark vNode.suspenseState = null; vNode.changeList = null; vNode.effectList = null; From 858f669bf7844e312937ef6137d9aff92f4ac98f Mon Sep 17 00:00:00 2001 From: * <8> Date: Fri, 1 Apr 2022 14:45:57 +0800 Subject: [PATCH 3/9] Match-id-39ccbc96998f49df9e8a525398082b5413bf72fe --- libs/horizon/src/renderer/ContextSaver.ts | 76 ++++--------------- libs/horizon/src/renderer/TreeBuilder.ts | 4 +- .../src/renderer/render/BaseComponent.ts | 5 +- .../src/renderer/render/ClassComponent.ts | 6 +- .../src/renderer/render/ContextProvider.ts | 11 ++- .../horizon/src/renderer/render/ForwardRef.ts | 4 +- .../src/renderer/render/FunctionComponent.ts | 27 ++----- .../src/renderer/render/SuspenseComponent.ts | 20 +++-- 8 files changed, 42 insertions(+), 111 deletions(-) diff --git a/libs/horizon/src/renderer/ContextSaver.ts b/libs/horizon/src/renderer/ContextSaver.ts index ef243cce..932a6ffa 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.contexts = ctxNamespace; + ctxNamespace = nextContext; } // bubble阶段恢复 -function resetNamespaceCtx(vNode: VNode) { - ctxNamespace = getContext(vNode, CTX_NAMESPACE); +export function resetNamespaceCtx(vNode: VNode) { + ctxNamespace = vNode.contexts; } -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.contexts = 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.contexts; } // 在局部更新时,恢复父节点的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.contexts = 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/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/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 f0056f04..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'; @@ -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) { @@ -136,7 +135,6 @@ 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) { @@ -172,4 +170,4 @@ export function captureRender(processing: VNode): VNode | null { } } -export function bubbleRender(processing: VNode) {} +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 563cda52..128f100e 100644 --- a/libs/horizon/src/renderer/render/FunctionComponent.ts +++ b/libs/horizon/src/renderer/render/FunctionComponent.ts @@ -5,7 +5,6 @@ import { resetDepContexts } from '../components/context/Context'; 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,22 +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 { - isCanReuse = false; - } - - return isCanReuse; +function checkIfCanReuseChildren(processing: VNode) { + return !processing.isCreated && + processing.oldProps === processing.props && + !processing.isDepContextChange; } export function setStateChange(isUpdate) { @@ -46,7 +33,6 @@ export function captureFunctionComponent( processing: VNode, funcComp: any, nextProps: any, - shouldUpdate?: boolean, ) { // 函数组件内已完成异步动作 if (processing.isSuspended) { @@ -58,7 +44,7 @@ export function captureFunctionComponent( } resetDepContexts(processing); - const isCanReuse = checkIfCanReuseChildren(processing, shouldUpdate); + const isCanReuse = checkIfCanReuseChildren(processing); // 在执行exeFunctionHook前先设置stateChange为false setStateChange(false); @@ -80,7 +66,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 = @@ -92,7 +78,6 @@ export function captureRender(processing: VNode, shouldUpdate?: boolean): VNode processing, Component, resolvedProps, - shouldUpdate, ); } diff --git a/libs/horizon/src/renderer/render/SuspenseComponent.ts b/libs/horizon/src/renderer/render/SuspenseComponent.ts index 225312aa..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, 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 { @@ -101,7 +100,7 @@ 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.suspenseState.promiseResolved) { @@ -130,7 +129,6 @@ export function captureRender(processing: VNode, shouldUpdate: boolean): Array Date: Fri, 1 Apr 2022 14:48:03 +0800 Subject: [PATCH 4/9] Match-id-2d3ad46152443e0e465ba7f54dc734e3cf1694af --- libs/horizon/src/renderer/ContextSaver.ts | 10 +++++----- libs/horizon/src/renderer/vnode/VNode.ts | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/libs/horizon/src/renderer/ContextSaver.ts b/libs/horizon/src/renderer/ContextSaver.ts index 932a6ffa..6fd6ef9a 100644 --- a/libs/horizon/src/renderer/ContextSaver.ts +++ b/libs/horizon/src/renderer/ContextSaver.ts @@ -17,14 +17,14 @@ let ctxNamespace = ''; export function setNamespaceCtx(vNode: VNode, dom?: Container) { const nextContext = getNSCtx(ctxNamespace, vNode.type, dom); - vNode.contexts = ctxNamespace; + vNode.context = ctxNamespace; ctxNamespace = nextContext; } // bubble阶段恢复 export function resetNamespaceCtx(vNode: VNode) { - ctxNamespace = vNode.contexts; + ctxNamespace = vNode.context; } export function getNamespaceCtx(): string { @@ -34,7 +34,7 @@ export function getNamespaceCtx(): string { export function setContext(providerVNode: VNode, nextValue: T) { const context: ContextType = providerVNode.type._context; - providerVNode.contexts = context.value; + providerVNode.context = context.value; context.value = nextValue; } @@ -42,7 +42,7 @@ export function setContext(providerVNode: VNode, nextValue: T) { export function resetContext(providerVNode: VNode) { const context: ContextType = providerVNode.type._context; - context.value = providerVNode.contexts; + context.value = providerVNode.context; } // 在局部更新时,恢复父节点的context @@ -51,7 +51,7 @@ export function recoverParentContext(vNode: VNode) { while (parent !== null) { if (parent.tag === ContextProvider) { - parent.contexts = parent.props.value; + parent.context = parent.props.value; } parent = parent.parent; } diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 7189db06..bbde565f 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -55,7 +55,7 @@ export class VNode { task: any; // 使用这个变量来记录修改前的值,用于恢复。 - contexts: any; + context: any; // 因为LazyComponent会修改tag和type属性,为了能识别,增加一个属性 isLazyComponent: boolean; @@ -93,7 +93,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; @@ -112,16 +112,16 @@ 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; @@ -137,7 +137,7 @@ export class VNode { }; break; case ContextProvider: - this.contexts = null; + this.context = null; break; case MemoComponent: this.effectList = null; From 422726a88f8992010fd9090004d3a059b86160cd Mon Sep 17 00:00:00 2001 From: * <8> Date: Fri, 1 Apr 2022 16:19:42 +0800 Subject: [PATCH 5/9] Match-id-52f4df6a91e64babf85d2b4fe312dff944c9a1b2 --- libs/horizon/index.ts | 9 +++++ libs/horizon/src/external/Horizon.ts | 15 +++++++-- .../src/renderer/hooks/HookExternal.ts | 23 +++++++------ libs/horizon/src/renderer/hooks/HookMain.ts | 19 ----------- .../horizon/src/renderer/hooks/HookMapping.ts | 21 ------------ .../src/renderer/render/FunctionComponent.ts | 4 ++- libs/horizon/src/renderer/vnode/VNode.ts | 6 ++++ .../ComponentTest/HookTest/UseContext.test.js | 4 +-- .../ComponentTest/HookTest/UseEffect.test.js | 6 ++-- .../HookTest/UseImperativeHandle.test.js | 6 ++-- .../HookTest/UseLayoutEffect.test.js | 6 ++-- .../ComponentTest/HookTest/UseMemo.test.js | 2 +- .../ComponentTest/HookTest/UseRef.test.js | 2 +- .../ComponentTest/HookTest/UseState.test.js | 6 ++-- .../__tests__/EventTest/FocusEvent.test.js | 3 +- scripts/__tests__/jest/Text.js | 9 ----- scripts/__tests__/jest/commonComponents.js | 20 +++++++++++ scripts/__tests__/jest/customMatcher.js | 33 ------------------- scripts/__tests__/jest/jestSetting.js | 22 ++++++++++++- scripts/__tests__/jest/testUtils.js | 11 +++++-- 20 files changed, 108 insertions(+), 119 deletions(-) delete mode 100644 libs/horizon/src/renderer/hooks/HookMapping.ts delete mode 100644 scripts/__tests__/jest/Text.js create mode 100644 scripts/__tests__/jest/commonComponents.js delete mode 100644 scripts/__tests__/jest/customMatcher.js diff --git a/libs/horizon/index.ts b/libs/horizon/index.ts index 64762f9e..cc37e0d0 100644 --- a/libs/horizon/index.ts +++ b/libs/horizon/index.ts @@ -23,6 +23,9 @@ import { createElement, cloneElement, isValidElement, + act, + launchUpdateFromVNode as _launchUpdateFromVNode, + getProcessingVNode as _getProcessingVNode, } from './src/external/Horizon'; import { @@ -63,6 +66,9 @@ const Horizon = { unstable_batchedUpdates, findDOMNode, unmountComponentAtNode, + act, + _launchUpdateFromVNode, + _getProcessingVNode, }; export { @@ -95,6 +101,9 @@ export { unstable_batchedUpdates, findDOMNode, unmountComponentAtNode, + act, + _launchUpdateFromVNode, + _getProcessingVNode, }; export default Horizon; diff --git a/libs/horizon/src/external/Horizon.ts b/libs/horizon/src/external/Horizon.ts index 623bfbb4..6644f2a0 100644 --- a/libs/horizon/src/external/Horizon.ts +++ b/libs/horizon/src/external/Horizon.ts @@ -17,7 +17,6 @@ import {createContext} from '../renderer/components/context/CreateContext'; import {lazy} from '../renderer/components/Lazy'; import {forwardRef} from '../renderer/components/ForwardRef'; import {memo} from '../renderer/components/Memo'; -import hookMapping from '../renderer/hooks/HookMapping'; import { useCallback, @@ -30,6 +29,16 @@ import { useRef, useState, } from '../renderer/hooks/HookExternal'; +import {launchUpdateFromVNode, asyncUpdates} from '../renderer/TreeBuilder'; +import {callRenderQueueImmediate} from '../renderer/taskExecutor/RenderQueue'; +import {runAsyncEffects} from '../renderer/submit/HookEffectHandler'; +import { getProcessingVNode } from '../renderer/hooks/BaseHook'; + +const act = (fun) => { + asyncUpdates(fun); + callRenderQueueImmediate(); + runAsyncEffects(); +} export { Children, @@ -56,5 +65,7 @@ export { createElement, cloneElement, isValidElement, - hookMapping, + act, + launchUpdateFromVNode, + getProcessingVNode, }; diff --git a/libs/horizon/src/renderer/hooks/HookExternal.ts b/libs/horizon/src/renderer/hooks/HookExternal.ts index fc2805ab..e4610f43 100644 --- a/libs/horizon/src/renderer/hooks/HookExternal.ts +++ b/libs/horizon/src/renderer/hooks/HookExternal.ts @@ -1,17 +1,15 @@ import type {ContextType} from '../Types'; -import hookMapping from './HookMapping'; import {useRefImpl} from './UseRefHook'; import {useEffectImpl, useLayoutEffectImpl} from './UseEffectHook'; import {useCallbackImpl} from './UseCallbackHook'; import {useMemoImpl} from './UseMemoHook'; import {useImperativeHandleImpl} from './UseImperativeHook'; - -const { - UseContextHookMapping, - UseReducerHookMapping, - UseStateHookMapping -} = hookMapping; +import {useReducerImpl} from './UseReducerHook'; +import {useStateImpl} from './UseStateHook'; +import {getNewContext} from '../components/context/Context'; +import {getProcessingVNode} from './BaseHook'; +import {Ref, Trigger} from './HookType'; type BasicStateAction = ((S) => S) | S; type Dispatch = (A) => void; @@ -20,22 +18,23 @@ type Dispatch = (A) => void; export function useContext( Context: ContextType, ): T { - return UseContextHookMapping.val.useContext(Context); + const processingVNode = getProcessingVNode(); + return getNewContext(processingVNode!, Context, true); } export function useState(initialState: (() => S) | S,): [S, Dispatch>] { - return UseStateHookMapping.val.useState(initialState); + return useStateImpl(initialState); } export function useReducer( reducer: (S, A) => S, initialArg: I, init?: (I) => S, -): [S, Dispatch] { - return UseReducerHookMapping.val.useReducer(reducer, initialArg, init); +): [S, Trigger] | void { + return useReducerImpl(reducer, initialArg, init); } -export function useRef(initialValue: T): {current: T} { +export function useRef(initialValue: T): Ref { return useRefImpl(initialValue); } diff --git a/libs/horizon/src/renderer/hooks/HookMain.ts b/libs/horizon/src/renderer/hooks/HookMain.ts index f4c568ea..675afdbb 100644 --- a/libs/horizon/src/renderer/hooks/HookMain.ts +++ b/libs/horizon/src/renderer/hooks/HookMain.ts @@ -1,22 +1,11 @@ import type {VNode} from '../Types'; -import hookMapping from './HookMapping'; -const { - UseStateHookMapping, - UseReducerHookMapping, - UseContextHookMapping, -} = hookMapping; - -import {getNewContext} from '../components/context/Context'; import { getLastTimeHook, - getProcessingVNode, setLastTimeHook, setProcessingVNode, setCurrentHook, getNextHook } from './BaseHook'; -import {useStateImpl} from './UseStateHook'; -import {useReducerImpl} from './UseReducerHook'; import {HookStage, setHookStage} from './HookStage'; // hook对外入口 @@ -29,9 +18,6 @@ export function exeFunctionHook, Arg>( // 重置全局变量 resetGlobalVariable(); - // 初始化hook实现函数 - initHookMapping(); - setProcessingVNode(processing); processing.oldHooks = processing.hooks; @@ -71,8 +57,3 @@ function resetGlobalVariable() { setCurrentHook(null); } -export function initHookMapping() { - UseContextHookMapping.val = {useContext: context => getNewContext(getProcessingVNode(), context, true)}; - UseReducerHookMapping.val = {useReducer: useReducerImpl}; - UseStateHookMapping.val = {useState: useStateImpl}; -} diff --git a/libs/horizon/src/renderer/hooks/HookMapping.ts b/libs/horizon/src/renderer/hooks/HookMapping.ts deleted file mode 100644 index 2205d029..00000000 --- a/libs/horizon/src/renderer/hooks/HookMapping.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * 暂时用于解决测试代码无法运行问题,估计是:测试代码会循环或者重复依赖 - */ - -import type { - UseContextHookType, - UseReducerHookType, - UseStateHookType -} from '../Types'; - -const UseStateHookMapping: {val: (null | UseStateHookType)} = {val: null}; -const UseReducerHookMapping: {val: (null | UseReducerHookType)} = {val: null}; -const UseContextHookMapping: {val: (null | UseContextHookType)} = {val: null}; - -const hookMapping = { - UseStateHookMapping, - UseReducerHookMapping, - UseContextHookMapping, -} - -export default hookMapping; diff --git a/libs/horizon/src/renderer/render/FunctionComponent.ts b/libs/horizon/src/renderer/render/FunctionComponent.ts index 8191293b..d7397e32 100644 --- a/libs/horizon/src/renderer/render/FunctionComponent.ts +++ b/libs/horizon/src/renderer/render/FunctionComponent.ts @@ -76,12 +76,14 @@ export function captureFunctionComponent( ); // 这里需要判断是否可以复用,因为函数组件比起其他组价,多了context和stateChange两个因素 - if (isCanReuse && !isStateChange()) { + if (isCanReuse && !isStateChange() && !processing.isStoreChange) { FlagUtils.removeFlag(processing, Update); return onlyUpdateChildVNodes(processing); } + processing.isStoreChange = false; + processing.child = createChildrenByDiff(processing, processing.child, newElements, !processing.isCreated); return processing.child; } diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 8c98e673..1bd90f75 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -66,6 +66,10 @@ export class VNode { belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用 + // 状态管理器使用 + isStoreChange: boolean; + functionToObserver: FunctionToObserver | null; // 记录这个函数组件依赖哪些Observer + constructor(tag: VNodeTag, props: any, key: null | string, realNode) { this.tag = tag; // 对应组件的类型,比如ClassComponent等 this.key = key; @@ -90,6 +94,8 @@ export class VNode { this.depContexts = null; this.isDepContextChange = false; this.oldHooks = null; + this.isStoreChange = false; + this.functionToObserver = null; break; case ClassComponent: this.realNode = null; diff --git a/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js index d94443b7..7cb53f24 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js @@ -1,9 +1,7 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; -import { act } from '../../jest/customMatcher'; describe('useContext Hook Test', () => { - const { useState, useContext } = Horizon; - const { unmountComponentAtNode } = Horizon; + const { useState, useContext, act, unmountComponentAtNode } = Horizon; it('简单使用useContext', () => { const LanguageTypes = { diff --git a/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js b/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js index b1bc34fe..f5b66614 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js @@ -1,7 +1,6 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import { act } from '../../jest/customMatcher'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useEffect Hook Test', () => { const { @@ -9,7 +8,8 @@ describe('useEffect Hook Test', () => { useLayoutEffect, useState, memo, - forwardRef + forwardRef, + act, } = Horizon; it('简单使用useEffect', () => { diff --git a/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js b/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js index b7f5d6e8..99a7af6a 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js @@ -1,13 +1,13 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import { act } from '../../jest/customMatcher'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useImperativeHandle Hook Test', () => { const { useState, useImperativeHandle, - forwardRef + forwardRef, + act, } = Horizon; const { unmountComponentAtNode } = Horizon; diff --git a/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js b/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js index 84226eac..759f6cde 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js @@ -1,13 +1,13 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import { act } from '../../jest/customMatcher'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useLayoutEffect Hook Test', () => { const { useState, useEffect, - useLayoutEffect + useLayoutEffect, + act, } = Horizon; it('简单使用useLayoutEffect', () => { diff --git a/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js b/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js index 41882449..0e144f70 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js @@ -1,6 +1,6 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useMemo Hook Test', () => { const { useMemo, useState } = Horizon; diff --git a/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js b/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js index b2bab667..a9defa95 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js @@ -1,6 +1,6 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useRef Hook Test', () => { const { useState, useRef } = Horizon; diff --git a/scripts/__tests__/ComponentTest/HookTest/UseState.test.js b/scripts/__tests__/ComponentTest/HookTest/UseState.test.js index b9e1d950..7e485671 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseState.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseState.test.js @@ -1,14 +1,14 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import { act } from '../../jest/customMatcher'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useState Hook Test', () => { const { useState, forwardRef, useImperativeHandle, - memo + memo, + act, } = Horizon; it('简单使用useState', () => { diff --git a/scripts/__tests__/EventTest/FocusEvent.test.js b/scripts/__tests__/EventTest/FocusEvent.test.js index ee5e3c46..d796960e 100644 --- a/scripts/__tests__/EventTest/FocusEvent.test.js +++ b/scripts/__tests__/EventTest/FocusEvent.test.js @@ -1,6 +1,5 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../jest/logUtils'; -import { act } from '../jest/customMatcher'; describe('合成焦点事件', () => { @@ -43,4 +42,4 @@ describe('合成焦点事件', () => { 'onBlur: blur', ]); }) -}) \ No newline at end of file +}) diff --git a/scripts/__tests__/jest/Text.js b/scripts/__tests__/jest/Text.js deleted file mode 100644 index 781c6d5a..00000000 --- a/scripts/__tests__/jest/Text.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as Horizon from '@cloudsop/horizon/index.ts'; -import * as LogUtils from '../jest/logUtils'; - -const Text = (props) => { - LogUtils.log(props.text); - return

{props.text}

; -}; - -export default Text; diff --git a/scripts/__tests__/jest/commonComponents.js b/scripts/__tests__/jest/commonComponents.js new file mode 100644 index 00000000..138e18e7 --- /dev/null +++ b/scripts/__tests__/jest/commonComponents.js @@ -0,0 +1,20 @@ +import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as LogUtils from './logUtils'; + +export const App = (props) => { + const Parent = props.parent; + const Child = props.child; + + return ( +
+ + + +
+ ); +} + +export const Text = (props) => { + LogUtils.log(props.text); + return

{props.text}

; +} diff --git a/scripts/__tests__/jest/customMatcher.js b/scripts/__tests__/jest/customMatcher.js deleted file mode 100644 index 1f6a96fb..00000000 --- a/scripts/__tests__/jest/customMatcher.js +++ /dev/null @@ -1,33 +0,0 @@ -import { runAsyncEffects } from '../../../libs/horizon/src/renderer/submit/HookEffectHandler'; -import { callRenderQueueImmediate } from '../../../libs/horizon/src/renderer/taskExecutor/RenderQueue'; -import { asyncUpdates } from '../../../libs/horizon/src/renderer/TreeBuilder'; - -function runAssertion(fn) { - try { - fn(); - } catch (error) { - return { - pass: false, - message: () => error.message, - }; - } - return { pass: true }; -} - -function toMatchValue(LogUtils, expectedValues) { - return runAssertion(() => { - const actualValues = LogUtils.getAndClear(); - expect(actualValues).toEqual(expectedValues); - }); -} - -const act = (fun) => { - asyncUpdates(fun); - callRenderQueueImmediate(); - runAsyncEffects(); -} - -module.exports = { - toMatchValue, - act -}; diff --git a/scripts/__tests__/jest/jestSetting.js b/scripts/__tests__/jest/jestSetting.js index deda8dd9..78773f65 100644 --- a/scripts/__tests__/jest/jestSetting.js +++ b/scripts/__tests__/jest/jestSetting.js @@ -17,7 +17,27 @@ global.afterEach(() => { LogUtils.clear(); }); + +function runAssertion(fn) { + try { + fn(); + } catch (error) { + return { + pass: false, + message: () => error.message, + }; + } + return { pass: true }; +} + +function toMatchValue(LogUtils, expectedValues) { + return runAssertion(() => { + const actualValues = LogUtils.getAndClear(); + expect(actualValues).toEqual(expectedValues); + }); +} + // 使Jest感知自定义匹配器 expect.extend({ - ...require('./customMatcher'), + toMatchValue, }); diff --git a/scripts/__tests__/jest/testUtils.js b/scripts/__tests__/jest/testUtils.js index 8bab93d9..4afda9da 100644 --- a/scripts/__tests__/jest/testUtils.js +++ b/scripts/__tests__/jest/testUtils.js @@ -9,7 +9,7 @@ export const stopBubbleOrCapture = (e, value) => { export const getEventListeners = (dom) => { let ret = true let keyArray = []; - for (var key in dom) { + for (let key in dom) { keyArray.push(key); } try { @@ -23,4 +23,11 @@ export const getEventListeners = (dom) => { } return ret; -}; \ No newline at end of file +}; + +export function triggerClickEvent(container, id) { + const event = new MouseEvent('click', { + bubbles: true, + }); + container.querySelector(`#${id}`).dispatchEvent(event); +} From 238e438a749288931aac0c5f7d176596ecfad280 Mon Sep 17 00:00:00 2001 From: * <8> Date: Fri, 1 Apr 2022 17:46:58 +0800 Subject: [PATCH 6/9] Match-id-ade86cdcab051f0fa0d646c659870f6823ac1a97 --- libs/horizon/index.ts | 49 ++++++++----- .../dom/valueHandler/OptionValueHandler.ts | 6 +- libs/horizon/src/external/Horizon.ts | 71 ------------------- 3 files changed, 33 insertions(+), 93 deletions(-) delete mode 100644 libs/horizon/src/external/Horizon.ts diff --git a/libs/horizon/index.ts b/libs/horizon/index.ts index cc37e0d0..a7df0d8b 100644 --- a/libs/horizon/index.ts +++ b/libs/horizon/index.ts @@ -1,12 +1,20 @@ import { - Children, - createRef, - Component, - PureComponent, - createContext, - forwardRef, - lazy, - memo, + TYPE_FRAGMENT as Fragment, + TYPE_PROFILER as Profiler, + TYPE_STRICT_MODE as StrictMode, + TYPE_SUSPENSE as Suspense, +} from './src/external/JSXElementType'; + +import { Component, PureComponent } from './src/renderer/components/BaseClassComponent'; +import { createRef } from './src/renderer/components/CreateRef'; +import { Children } from './src/external/ChildrenUtil'; +import { createElement, cloneElement, isValidElement } from './src/external/JSXElement'; +import { createContext } from './src/renderer/components/context/CreateContext'; +import { lazy } from './src/renderer/components/Lazy'; +import { forwardRef } from './src/renderer/components/ForwardRef'; +import { memo } from './src/renderer/components/Memo'; + +import { useCallback, useContext, useEffect, @@ -16,17 +24,18 @@ import { useReducer, useRef, useState, - Fragment, - Profiler, - StrictMode, - Suspense, - createElement, - cloneElement, - isValidElement, - act, - launchUpdateFromVNode as _launchUpdateFromVNode, - getProcessingVNode as _getProcessingVNode, -} from './src/external/Horizon'; +} from './src/renderer/hooks/HookExternal'; +import { launchUpdateFromVNode as _launchUpdateFromVNode, asyncUpdates } from './src/renderer/TreeBuilder'; +import { callRenderQueueImmediate } from './src/renderer/taskExecutor/RenderQueue'; +import { runAsyncEffects } from './src/renderer/submit/HookEffectHandler'; +import { getProcessingVNode as _getProcessingVNode } from './src/renderer/hooks/BaseHook'; + +// act用于测试,作用是:如果fun触发了刷新(包含了异步刷新),可以保证在act后面的代码是在刷新完成后才执行。 +const act = fun => { + asyncUpdates(fun); + callRenderQueueImmediate(); + runAsyncEffects(); +}; import { render, @@ -102,6 +111,8 @@ export { findDOMNode, unmountComponentAtNode, act, + + // 暂时给HorizonX使用 _launchUpdateFromVNode, _getProcessingVNode, }; diff --git a/libs/horizon/src/dom/valueHandler/OptionValueHandler.ts b/libs/horizon/src/dom/valueHandler/OptionValueHandler.ts index bd487d4e..12cc6e9a 100644 --- a/libs/horizon/src/dom/valueHandler/OptionValueHandler.ts +++ b/libs/horizon/src/dom/valueHandler/OptionValueHandler.ts @@ -1,10 +1,10 @@ -import * as Horizon from '../../external/Horizon'; -import {IProperty} from '../utils/Interface'; +import { Children } from '../../external/ChildrenUtil'; +import { IProperty } from '../utils/Interface'; // 把 const a = 'a'; 转成 giraffe function concatChildren(children) { let content = ''; - Horizon.Children.forEach(children, function(child) { + Children.forEach(children, function(child) { content += child; }); diff --git a/libs/horizon/src/external/Horizon.ts b/libs/horizon/src/external/Horizon.ts deleted file mode 100644 index 6644f2a0..00000000 --- a/libs/horizon/src/external/Horizon.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { - TYPE_FRAGMENT, - TYPE_PROFILER, - TYPE_STRICT_MODE, - TYPE_SUSPENSE, -} from './JSXElementType'; - -import {Component, PureComponent} from '../renderer/components/BaseClassComponent'; -import {createRef} from '../renderer/components/CreateRef'; -import {Children} from './ChildrenUtil'; -import { - createElement, - cloneElement, - isValidElement, -} from './JSXElement'; -import {createContext} from '../renderer/components/context/CreateContext'; -import {lazy} from '../renderer/components/Lazy'; -import {forwardRef} from '../renderer/components/ForwardRef'; -import {memo} from '../renderer/components/Memo'; - -import { - useCallback, - useContext, - useEffect, - useImperativeHandle, - useLayoutEffect, - useMemo, - useReducer, - useRef, - useState, -} from '../renderer/hooks/HookExternal'; -import {launchUpdateFromVNode, asyncUpdates} from '../renderer/TreeBuilder'; -import {callRenderQueueImmediate} from '../renderer/taskExecutor/RenderQueue'; -import {runAsyncEffects} from '../renderer/submit/HookEffectHandler'; -import { getProcessingVNode } from '../renderer/hooks/BaseHook'; - -const act = (fun) => { - asyncUpdates(fun); - callRenderQueueImmediate(); - runAsyncEffects(); -} - -export { - Children, - createRef, - Component, - PureComponent, - createContext, - forwardRef, - lazy, - memo, - useCallback, - useContext, - useEffect, - useImperativeHandle, - useLayoutEffect, - useMemo, - useReducer, - useRef, - useState, - TYPE_FRAGMENT as Fragment, - TYPE_PROFILER as Profiler, - TYPE_STRICT_MODE as StrictMode, - TYPE_SUSPENSE as Suspense, - createElement, - cloneElement, - isValidElement, - act, - launchUpdateFromVNode, - getProcessingVNode, -}; From e0ac32cda970fc1d29d6287651e1811aedc06ba0 Mon Sep 17 00:00:00 2001 From: * <8> Date: Thu, 7 Apr 2022 19:15:39 +0800 Subject: [PATCH 7/9] Match-id-30ecadc80ad5388949a0fc159b3355ae65b8f9b3 --- .eslintrc.js | 25 +++++++------------ libs/horizon/src/dom/utils/Common.ts | 2 +- .../src/renderer/vnode/VNodeCreator.ts | 6 ++--- .../src/renderer/vnode/VNodeShouldUpdate.ts | 2 +- 4 files changed, 14 insertions(+), 21 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index ab410fd1..7aedabc1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,19 +1,13 @@ module.exports = { extends: [ 'eslint:recommended', - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended", + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', 'prettier', ], root: true, - plugins: [ - 'jest', - 'no-for-of-loops', - 'no-function-declare-after-return', - 'react', - '@typescript-eslint', - ], + plugins: ['jest', 'no-for-of-loops', 'no-function-declare-after-return', 'react', '@typescript-eslint'], parser: '@typescript-eslint/parser', parserOptions: { @@ -34,8 +28,8 @@ module.exports = { rules: { '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-non-null-assertion': 'off', - 'semi': ['warn', 'always'], - 'quotes': ['warn', 'single'], + semi: ['warn', 'always'], + quotes: ['warn', 'single'], 'accessor-pairs': 'off', 'brace-style': ['error', '1tbs'], 'func-style': ['warn', 'declaration', { allowArrowFunctions: true }], @@ -44,19 +38,18 @@ module.exports = { // 尾随逗号 'comma-dangle': ['error', 'only-multiline'], + 'no-constant-condition': 'off', 'no-for-of-loops/no-for-of-loops': 'error', 'no-function-declare-after-return/no-function-declare-after-return': 'error', }, globals: { - isDev: true + isDev: true, }, overrides: [ { - files: [ - 'scripts/__tests__/**/*.js' - ], + files: ['scripts/__tests__/**/*.js'], globals: { - container: true + container: true, }, }, ], diff --git a/libs/horizon/src/dom/utils/Common.ts b/libs/horizon/src/dom/utils/Common.ts index 09602c82..dae06d2b 100644 --- a/libs/horizon/src/dom/utils/Common.ts +++ b/libs/horizon/src/dom/utils/Common.ts @@ -6,7 +6,7 @@ import {Props} from '../DOMOperator'; * @param doc 指定 document */ export function getFocusedDom(doc?: Document): HorizonDom | null { - let currentDocument = doc ?? document; + const currentDocument = doc ?? document; return currentDocument.activeElement ?? currentDocument.body; } diff --git a/libs/horizon/src/renderer/vnode/VNodeCreator.ts b/libs/horizon/src/renderer/vnode/VNodeCreator.ts index 932c7fa7..8b7ad630 100644 --- a/libs/horizon/src/renderer/vnode/VNodeCreator.ts +++ b/libs/horizon/src/renderer/vnode/VNodeCreator.ts @@ -38,7 +38,7 @@ const typeMap = { [TYPE_LAZY]: LazyComponent, }; -function newVirtualNode (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); } @@ -201,7 +201,7 @@ export function onlyUpdateChildVNodes(processing: VNode): VNode | null { sibling = sibling.next; } } - } + }; putChildrenIntoQueue(processing.child); @@ -210,7 +210,7 @@ export function onlyUpdateChildVNodes(processing: VNode): VNode | null { markVNodePath(vNode); - putChildrenIntoQueue(vNode) + putChildrenIntoQueue(vNode); } } // 子树无需工作 diff --git a/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts b/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts index 72afb236..ec5dce84 100644 --- a/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts +++ b/libs/horizon/src/renderer/vnode/VNodeShouldUpdate.ts @@ -53,7 +53,7 @@ export function setParentsChildShouldUpdate(parent: VNode | null) { // 设置节点的所有父节点的childShouldUpdate export function updateParentsChildShouldUpdate(vNode: VNode) { let node = vNode.parent; - let isShouldUpdate = vNode.shouldUpdate || vNode.childShouldUpdate; + const isShouldUpdate = vNode.shouldUpdate || vNode.childShouldUpdate; if (isShouldUpdate) { // 开始节点是shouldUpdate或childShouldUpdate // 更新从当前节点到根节点的childShouldUpdate为true From 3de86a411cc0651c9dc7b4ed9a892aa4c54c2112 Mon Sep 17 00:00:00 2001 From: * <8> Date: Wed, 23 Mar 2022 10:28:58 +0800 Subject: [PATCH 8/9] Match-id-a642a9a90565e0bc7897482ce61d1ac65f8120c5 --- libs/horizon/src/renderer/ContextSaver.ts | 2 +- .../ComponentTest/HookTest/UseContext.test.js | 48 +++++++++++++++---- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/libs/horizon/src/renderer/ContextSaver.ts b/libs/horizon/src/renderer/ContextSaver.ts index 6fd6ef9a..cd21da0b 100644 --- a/libs/horizon/src/renderer/ContextSaver.ts +++ b/libs/horizon/src/renderer/ContextSaver.ts @@ -51,7 +51,7 @@ export function recoverParentContext(vNode: VNode) { while (parent !== null) { if (parent.tag === ContextProvider) { - parent.context = parent.props.value; + setContext(parent, parent.props.value); } parent = parent.parent; } diff --git a/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js index d94443b7..c0fc0a53 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js @@ -2,7 +2,7 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import { act } from '../../jest/customMatcher'; describe('useContext Hook Test', () => { - const { useState, useContext } = Horizon; + const { useState, useContext, createContext } = Horizon; const { unmountComponentAtNode } = Horizon; it('简单使用useContext', () => { @@ -14,16 +14,12 @@ describe('useContext Hook Test', () => { const SystemLanguageContext = Horizon.createContext(defaultValue); const SystemLanguageProvider = ({ type, children }) => { - return ( - - {children} - - ); + return {children}; }; const TestFunction = () => { const context = useContext(SystemLanguageContext); return

{context.type}

; - } + }; let setValue; const App = () => { const [value, _setValue] = useState(LanguageTypes.JAVA); @@ -34,8 +30,8 @@ describe('useContext Hook Test', () => { - ) - } + ); + }; Horizon.render(, container); // 测试当Provider未提供时,获取到的默认值'JavaScript'。 expect(container.querySelector('p').innerHTML).toBe('JavaScript'); @@ -47,4 +43,38 @@ describe('useContext Hook Test', () => { act(() => setValue(LanguageTypes.JAVASCRIPT)); expect(container.querySelector('p').innerHTML).toBe('JavaScript'); }); + + it('更新后useContext仍能获取到context', () => { + const Context = createContext({}); + const ref = Horizon.createRef(); + + function App() { + return ( + + + + ); + } + + let update; + + function Child() { + const context = useContext(Context); + const [_, setState] = useState({}); + update = () => setState({}); + + return
{context.text}
; + } + + Horizon.render(, container); + expect(ref.current.innerHTML).toBe('context'); + + update(); + + expect(ref.current.innerHTML).toBe('context'); + }); }); From a7e601ad08ef1f54abfe84f89e7658a574ca0246 Mon Sep 17 00:00:00 2001 From: * <8> Date: Fri, 8 Apr 2022 14:27:16 +0800 Subject: [PATCH 9/9] Match-id-55d57069bcbae4d9269d2060b72e9784ec558608 --- scripts/__tests__/ComponentTest/HookTest/UseContext.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js index dfa2aec9..bce485c6 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js @@ -1,6 +1,6 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; -describe('useContext Hook Test', () => {; +describe('useContext Hook Test', () => { const { useState, useContext, createContext, act, unmountComponentAtNode } = Horizon; it('简单使用useContext', () => {