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