Match-id-0cbbf861f75e90b3561570fa0ba46e24a1a51c30

This commit is contained in:
* 2022-04-11 11:04:42 +08:00 committed by *
commit 54235cd0e3
38 changed files with 320 additions and 449 deletions

View File

@ -1,19 +1,13 @@
module.exports = { module.exports = {
extends: [ extends: [
'eslint:recommended', 'eslint:recommended',
"plugin:@typescript-eslint/eslint-recommended", 'plugin:@typescript-eslint/eslint-recommended',
"plugin:@typescript-eslint/recommended", 'plugin:@typescript-eslint/recommended',
'prettier', 'prettier',
], ],
root: true, root: true,
plugins: [ plugins: ['jest', 'no-for-of-loops', 'no-function-declare-after-return', 'react', '@typescript-eslint'],
'jest',
'no-for-of-loops',
'no-function-declare-after-return',
'react',
'@typescript-eslint',
],
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
parserOptions: { parserOptions: {
@ -34,8 +28,8 @@ module.exports = {
rules: { rules: {
'@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-non-null-assertion': 'off',
'semi': ['warn', 'always'], semi: ['warn', 'always'],
'quotes': ['warn', 'single'], quotes: ['warn', 'single'],
'accessor-pairs': 'off', 'accessor-pairs': 'off',
'brace-style': ['error', '1tbs'], 'brace-style': ['error', '1tbs'],
'func-style': ['warn', 'declaration', { allowArrowFunctions: true }], 'func-style': ['warn', 'declaration', { allowArrowFunctions: true }],
@ -44,19 +38,18 @@ module.exports = {
// 尾随逗号 // 尾随逗号
'comma-dangle': ['error', 'only-multiline'], 'comma-dangle': ['error', 'only-multiline'],
'no-constant-condition': 'off',
'no-for-of-loops/no-for-of-loops': 'error', 'no-for-of-loops/no-for-of-loops': 'error',
'no-function-declare-after-return/no-function-declare-after-return': 'error', 'no-function-declare-after-return/no-function-declare-after-return': 'error',
}, },
globals: { globals: {
isDev: true isDev: true,
}, },
overrides: [ overrides: [
{ {
files: [ files: ['scripts/__tests__/**/*.js'],
'scripts/__tests__/**/*.js'
],
globals: { globals: {
container: true container: true,
}, },
}, },
], ],

View File

@ -1,12 +1,20 @@
import { import {
Children, TYPE_FRAGMENT as Fragment,
createRef, TYPE_PROFILER as Profiler,
Component, TYPE_STRICT_MODE as StrictMode,
PureComponent, TYPE_SUSPENSE as Suspense,
createContext, } from './src/external/JSXElementType';
forwardRef,
lazy, import { Component, PureComponent } from './src/renderer/components/BaseClassComponent';
memo, 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, useCallback,
useContext, useContext,
useEffect, useEffect,
@ -16,14 +24,18 @@ import {
useReducer, useReducer,
useRef, useRef,
useState, useState,
Fragment, } from './src/renderer/hooks/HookExternal';
Profiler, import { launchUpdateFromVNode as _launchUpdateFromVNode, asyncUpdates } from './src/renderer/TreeBuilder';
StrictMode, import { callRenderQueueImmediate } from './src/renderer/taskExecutor/RenderQueue';
Suspense, import { runAsyncEffects } from './src/renderer/submit/HookEffectHandler';
createElement, import { getProcessingVNode as _getProcessingVNode } from './src/renderer/hooks/BaseHook';
cloneElement,
isValidElement, // act用于测试作用是如果fun触发了刷新包含了异步刷新可以保证在act后面的代码是在刷新完成后才执行。
} from './src/external/Horizon'; const act = fun => {
asyncUpdates(fun);
callRenderQueueImmediate();
runAsyncEffects();
};
import { import {
render, render,
@ -63,6 +75,9 @@ const Horizon = {
unstable_batchedUpdates, unstable_batchedUpdates,
findDOMNode, findDOMNode,
unmountComponentAtNode, unmountComponentAtNode,
act,
_launchUpdateFromVNode,
_getProcessingVNode,
}; };
export { export {
@ -95,6 +110,11 @@ export {
unstable_batchedUpdates, unstable_batchedUpdates,
findDOMNode, findDOMNode,
unmountComponentAtNode, unmountComponentAtNode,
act,
// 暂时给HorizonX使用
_launchUpdateFromVNode,
_getProcessingVNode,
}; };
export default Horizon; export default Horizon;

View File

@ -6,7 +6,7 @@ import {Props} from '../DOMOperator';
* @param doc document * @param doc document
*/ */
export function getFocusedDom(doc?: Document): HorizonDom | null { export function getFocusedDom(doc?: Document): HorizonDom | null {
let currentDocument = doc ?? document; const currentDocument = doc ?? document;
return currentDocument.activeElement ?? currentDocument.body; return currentDocument.activeElement ?? currentDocument.body;
} }

View File

@ -1,10 +1,10 @@
import * as Horizon from '../../external/Horizon'; import { Children } from '../../external/ChildrenUtil';
import { IProperty } from '../utils/Interface'; import { IProperty } from '../utils/Interface';
// 把 const a = 'a'; <option>gir{a}ffe</option> 转成 giraffe // 把 const a = 'a'; <option>gir{a}ffe</option> 转成 giraffe
function concatChildren(children) { function concatChildren(children) {
let content = ''; let content = '';
Horizon.Children.forEach(children, function(child) { Children.forEach(children, function(child) {
content += child; content += child;
}); });

View File

@ -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,
};

View File

@ -1,6 +1,6 @@
/** /**
* context * context
* begin阶段会修改一些全局的值complete阶段会恢复 * capture阶段会修改一些全局的值bubble阶段会恢复
*/ */
import type { VNode, ContextType } from './Types'; 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” // 保存的是“http://www.w3.org/1999/xhtml”或“http://www.w3.org/2000/svg”
// 用于识别是使用document.createElement()还是使用document.createElementNS()创建DOM // 用于识别是使用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 = ''; 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阶段设置 // capture阶段设置
function setNamespaceCtx(vNode: VNode, dom?: Container) { export function setNamespaceCtx(vNode: VNode, dom?: Container) {
const nextContext = getNSCtx(ctxNamespace, vNode.type, dom); const nextContext = getNSCtx(ctxNamespace, vNode.type, dom);
setContext(vNode, CTX_NAMESPACE, ctxNamespace); vNode.context = ctxNamespace;
ctxNamespace = nextContext; ctxNamespace = nextContext;
} }
// bubble阶段恢复 // bubble阶段恢复
function resetNamespaceCtx(vNode: VNode) { export function resetNamespaceCtx(vNode: VNode) {
ctxNamespace = getContext(vNode, CTX_NAMESPACE); ctxNamespace = vNode.context;
} }
function getNamespaceCtx(): string { export function getNamespaceCtx(): string {
return ctxNamespace; return ctxNamespace;
} }
function setContextCtx<T>(providerVNode: VNode, nextValue: T) { export function setContext<T>(providerVNode: VNode, nextValue: T) {
const context: ContextType<T> = providerVNode.type._context; const context: ContextType<T> = providerVNode.type._context;
setContext(providerVNode, CTX_CONTEXT, context.value); providerVNode.context = context.value;
context.value = nextValue; context.value = nextValue;
} }
function resetContextCtx(providerVNode: VNode) { export function resetContext(providerVNode: VNode) {
const context: ContextType<any> = providerVNode.type._context; const context: ContextType<any> = providerVNode.type._context;
context.value = getContext(providerVNode, CTX_CONTEXT); context.value = providerVNode.context;
} }
// 在局部更新时恢复父节点的context // 在局部更新时恢复父节点的context
function recoverParentsContextCtx(vNode: VNode) { export function recoverParentContext(vNode: VNode) {
let parent = vNode.parent; let parent = vNode.parent;
while (parent !== null) { while (parent !== null) {
if (parent.tag === ContextProvider) { if (parent.tag === ContextProvider) {
const newValue = parent.props.value; setContext(parent, parent.props.value);
setContextCtx(parent, newValue);
} }
parent = parent.parent; 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,
};

View File

@ -2,7 +2,7 @@
* *
*/ */
import type {VNode} from './Types'; import type { PromiseType, VNode } from './Types';
import type {Update} from './UpdateHandler'; import type {Update} from './UpdateHandler';
import {ClassComponent, TreeRoot} from './vnode/VNodeTags'; import {ClassComponent, TreeRoot} from './vnode/VNodeTags';
@ -62,7 +62,9 @@ function createClassErrorUpdate(
} }
return update; return update;
} }
function isPromise(error: any): error is PromiseType<any> {
return error !== null && typeof error === 'object' && typeof error.then === 'function'
}
// 处理capture和bubble阶段抛出的错误 // 处理capture和bubble阶段抛出的错误
export function handleRenderThrowError( export function handleRenderThrowError(
sourceVNode: VNode, sourceVNode: VNode,
@ -74,7 +76,7 @@ export function handleRenderThrowError(
sourceVNode.dirtyNodes = null; sourceVNode.dirtyNodes = null;
// error是个promise // error是个promise
if (error !== null && typeof error === 'object' && typeof error.then === 'function') { if (isPromise(error)) {
// 抛出异常的节点向上寻找是否有suspense组件 // 抛出异常的节点向上寻找是否有suspense组件
const foundSuspense = handleSuspenseChildThrowError(sourceVNode.parent, sourceVNode, error); const foundSuspense = handleSuspenseChildThrowError(sourceVNode.parent, sourceVNode, error);
if (foundSuspense) { if (foundSuspense) {

View File

@ -29,7 +29,7 @@ import {
isExecuting, isExecuting,
setExecuteMode setExecuteMode
} from './ExecuteMode'; } from './ExecuteMode';
import { recoverParentsContextCtx, resetNamespaceCtx, setNamespaceCtx } from './ContextSaver'; import { recoverParentContext, resetNamespaceCtx, setNamespaceCtx } from './ContextSaver';
import { import {
updateChildShouldUpdate, updateChildShouldUpdate,
updateParentsChildShouldUpdate, updateParentsChildShouldUpdate,
@ -231,7 +231,7 @@ function buildVNodeTree(treeRoot: VNode) {
} }
// 恢复父节点的context // 恢复父节点的context
recoverParentsContextCtx(startVNode); recoverParentContext(startVNode);
} }
// 重置环境变量,为重新进行深度遍历做准备 // 重置环境变量,为重新进行深度遍历做准备

View File

@ -54,3 +54,10 @@ export interface PromiseType<R> {
): void | PromiseType<U>; ): void | PromiseType<U>;
} }
export interface SuspenseState {
promiseSet: Set<PromiseType<any>> | null; // suspense组件的promise列表
childStatus: string;
oldChildStatus: string; // 上一次Suspense的Children是否显示
didCapture: boolean; // suspense是否捕获了异常
promiseResolved: boolean; // suspense的promise是否resolve
}

View File

@ -1,17 +1,15 @@
import type {ContextType} from '../Types'; import type {ContextType} from '../Types';
import hookMapping from './HookMapping';
import {useRefImpl} from './UseRefHook'; import {useRefImpl} from './UseRefHook';
import {useEffectImpl, useLayoutEffectImpl} from './UseEffectHook'; import {useEffectImpl, useLayoutEffectImpl} from './UseEffectHook';
import {useCallbackImpl} from './UseCallbackHook'; import {useCallbackImpl} from './UseCallbackHook';
import {useMemoImpl} from './UseMemoHook'; import {useMemoImpl} from './UseMemoHook';
import {useImperativeHandleImpl} from './UseImperativeHook'; import {useImperativeHandleImpl} from './UseImperativeHook';
import {useReducerImpl} from './UseReducerHook';
const { import {useStateImpl} from './UseStateHook';
UseContextHookMapping, import {getNewContext} from '../components/context/Context';
UseReducerHookMapping, import {getProcessingVNode} from './BaseHook';
UseStateHookMapping import {Ref, Trigger} from './HookType';
} = hookMapping;
type BasicStateAction<S> = ((S) => S) | S; type BasicStateAction<S> = ((S) => S) | S;
type Dispatch<A> = (A) => void; type Dispatch<A> = (A) => void;
@ -20,22 +18,23 @@ type Dispatch<A> = (A) => void;
export function useContext<T>( export function useContext<T>(
Context: ContextType<T>, Context: ContextType<T>,
): T { ): T {
return UseContextHookMapping.val.useContext(Context); const processingVNode = getProcessingVNode();
return getNewContext(processingVNode!, Context, true);
} }
export function useState<S>(initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] { export function useState<S>(initialState: (() => S) | S,): [S, Dispatch<BasicStateAction<S>>] {
return UseStateHookMapping.val.useState(initialState); return useStateImpl(initialState);
} }
export function useReducer<S, I, A>( export function useReducer<S, I, A>(
reducer: (S, A) => S, reducer: (S, A) => S,
initialArg: I, initialArg: I,
init?: (I) => S, init?: (I) => S,
): [S, Dispatch<A>] { ): [S, Trigger<A>] | void {
return UseReducerHookMapping.val.useReducer(reducer, initialArg, init); return useReducerImpl(reducer, initialArg, init);
} }
export function useRef<T>(initialValue: T): {current: T} { export function useRef<T>(initialValue: T): Ref<T> {
return useRefImpl(initialValue); return useRefImpl(initialValue);
} }

View File

@ -1,26 +1,15 @@
import type {VNode} from '../Types'; import type {VNode} from '../Types';
import hookMapping from './HookMapping';
const {
UseStateHookMapping,
UseReducerHookMapping,
UseContextHookMapping,
} = hookMapping;
import {getNewContext} from '../components/context/Context';
import { import {
getLastTimeHook, getLastTimeHook,
getProcessingVNode,
setLastTimeHook, setLastTimeHook,
setProcessingVNode, setProcessingVNode,
setCurrentHook, getNextHook setCurrentHook, getNextHook
} from './BaseHook'; } from './BaseHook';
import {useStateImpl} from './UseStateHook';
import {useReducerImpl} from './UseReducerHook';
import {HookStage, setHookStage} from './HookStage'; import {HookStage, setHookStage} from './HookStage';
// hook对外入口 // hook对外入口
export function exeFunctionHook<Props extends Record<string, any>, Arg>( export function runFunctionWithHooks<Props extends Record<string, any>, Arg>(
funcComp: (props: Props, arg: Arg) => any, funcComp: (props: Props, arg: Arg) => any,
props: Props, props: Props,
arg: Arg, arg: Arg,
@ -29,9 +18,6 @@ export function exeFunctionHook<Props extends Record<string, any>, Arg>(
// 重置全局变量 // 重置全局变量
resetGlobalVariable(); resetGlobalVariable();
// 初始化hook实现函数
initHookMapping();
setProcessingVNode(processing); setProcessingVNode(processing);
processing.oldHooks = processing.hooks; processing.oldHooks = processing.hooks;
@ -71,8 +57,3 @@ function resetGlobalVariable() {
setCurrentHook(null); setCurrentHook(null);
} }
export function initHookMapping() {
UseContextHookMapping.val = {useContext: context => getNewContext(getProcessingVNode(), context, true)};
UseReducerHookMapping.val = {useReducer: useReducerImpl};
UseStateHookMapping.val = {useState: useStateImpl};
}

View File

@ -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;

View File

@ -7,7 +7,7 @@ import {
TreeRoot, TreeRoot,
SuspenseComponent, SuspenseComponent,
} from '../vnode/VNodeTags'; } from '../vnode/VNodeTags';
import { getContextChangeCtx, setContextCtx, setNamespaceCtx } from '../ContextSaver'; import { setContext, setNamespaceCtx } from '../ContextSaver';
import { FlagUtils } from '../vnode/VNodeFlags'; import { FlagUtils } from '../vnode/VNodeFlags';
import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator'; import {onlyUpdateChildVNodes} from '../vnode/VNodeCreator';
import componentRenders from './index'; import componentRenders from './index';
@ -26,7 +26,7 @@ function handlerContext(processing: VNode) {
break; break;
case ContextProvider: { case ContextProvider: {
const newValue = processing.props.value; const newValue = processing.props.value;
setContextCtx(processing, newValue); setContext(processing, newValue);
break; break;
} }
// No Default // No Default
@ -41,7 +41,6 @@ export function captureVNode(processing: VNode): VNode | null {
if ( if (
!processing.isCreated && !processing.isCreated &&
processing.oldProps === processing.props && processing.oldProps === processing.props &&
!getContextChangeCtx() &&
!processing.shouldUpdate !processing.shouldUpdate
) { ) {
// 复用还需对stack进行处理 // 复用还需对stack进行处理

View File

@ -18,7 +18,6 @@ import { markRef } from './BaseComponent';
import { import {
processUpdates, processUpdates,
} from '../UpdateHandler'; } from '../UpdateHandler';
import { getContextChangeCtx } from '../ContextSaver';
import { setProcessingClassVNode } from '../GlobalVar'; import { setProcessingClassVNode } from '../GlobalVar';
import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator'; import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator';
import { createChildrenByDiff } from '../diff/nodeDiffComparator'; 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) { if (!processing.isCreated) {
processing.isCreated = true; processing.isCreated = true;
FlagUtils.markAddition(processing); FlagUtils.markAddition(processing);
} }
// 构造实例 // 构造实例
const inst = callConstructor(processing, clazz, nextProps); const inst = callConstructor(processing, ctor, nextProps);
inst.props = nextProps; inst.props = nextProps;
inst.state = processing.state; inst.state = processing.state;
inst.context = getCurrentContext(clazz, processing); inst.context = getCurrentContext(ctor, processing);
inst.refs = {}; inst.refs = {};
processUpdates(processing, inst, nextProps); processUpdates(processing, inst, nextProps);
inst.state = processing.state; inst.state = processing.state;
// 在调用类组建的渲染方法之前调用 并且在初始挂载及后续更新时都会被调用 // 在调用类组建的渲染方法之前调用 并且在初始挂载及后续更新时都会被调用
callDerivedStateFromProps(processing, clazz.getDerivedStateFromProps, nextProps); callDerivedStateFromProps(processing, ctor.getDerivedStateFromProps, nextProps);
callComponentWillMount(processing, inst, nextProps); callComponentWillMount(processing, inst, nextProps);
markComponentDidMount(processing); 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) { if (processing.isCreated) {
markComponentDidMount(processing); markComponentDidMount(processing);
} else if (processing.state !== processing.oldState || shouldUpdate) { } 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 { export function captureRender(processing: VNode): VNode | null {
let clazz = processing.type; const ctor = processing.type;
let nextProps = processing.props; let nextProps = processing.props;
if (processing.isLazyComponent) { if (processing.isLazyComponent) {
nextProps = mergeDefaultProps(clazz, nextProps); nextProps = mergeDefaultProps(ctor, nextProps);
if (processing.promiseResolve) { // 该函数被 lazy 组件使用,未加载的组件需要加载组件的真实内容
clazz = clazz._load(clazz._content);
}
} }
resetDepContexts(processing); 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的调用结果有关 // 通过 shouldUpdate 判断是否要复用 children该值和props,state,context的变化shouldComponentUpdate,forceUpdate api的调用结果有关
let shouldUpdate; let shouldUpdate;
const inst = processing.realNode; const inst = processing.realNode;
if (inst === null) { if (inst === null) {
// 挂载新组件,一定会更新 // 挂载新组件,一定会更新
mountInstance(clazz, processing, nextProps); mountInstance(ctor, processing, nextProps);
shouldUpdate = true; shouldUpdate = true;
} else { // 更新 } else { // 更新
const newContext = getCurrentContext(clazz, processing); const newContext = getCurrentContext(ctor, processing);
// 子节点抛出异常时如果本class是个捕获异常的处理节点这时候oldProps是null所以需要使用props // 子节点抛出异常时如果本class是个捕获异常的处理节点这时候oldProps是null所以需要使用props
const oldProps = (processing.flags & DidCapture) === DidCapture ? processing.props : processing.oldProps; 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则不需要更新 // 如果 props, state, context 都没有变化且 isForceUpdate 为 false则不需要更新
shouldUpdate = oldProps !== processing.props || shouldUpdate = oldProps !== processing.props ||
inst.state !== processing.state || inst.state !== processing.state ||
getContextChangeCtx() ||
processing.isForceUpdate; processing.isForceUpdate;
if (shouldUpdate) { if (shouldUpdate) {
// derivedStateFromProps会修改nextState因此需要调用 // derivedStateFromProps会修改nextState因此需要调用
callDerivedStateFromProps(processing, clazz.getDerivedStateFromProps, nextProps); callDerivedStateFromProps(processing, ctor.getDerivedStateFromProps, nextProps);
if (!processing.isForceUpdate) { if (!processing.isForceUpdate) {
// 业务可以通过 shouldComponentUpdate 函数进行优化阻止更新 // 业务可以通过 shouldComponentUpdate 函数进行优化阻止更新
shouldUpdate = callShouldComponentUpdate(processing, oldProps, nextProps, processing.state, newContext); shouldUpdate = callShouldComponentUpdate(processing, oldProps, nextProps, processing.state, newContext);
} }
if (shouldUpdate) { if (shouldUpdate) {
callUpdateLifeCycle(processing, nextProps, clazz); callUpdateLifeCycle(processing, nextProps, ctor);
} }
inst.state = processing.state; inst.state = processing.state;
inst.context = newContext; inst.context = newContext;
@ -162,16 +164,10 @@ export function captureRender(processing: VNode): VNode | null {
// 不复用 // 不复用
if (shouldUpdate) { if (shouldUpdate) {
return createChildren(clazz, processing); return createChildren(ctor, processing);
} else { } else {
return onlyUpdateChildVNodes(processing); return onlyUpdateChildVNodes(processing);
} }
} }
export function bubbleRender(processing: VNode) {} export function bubbleRender() {}
// 用于未完成的类组件
export function getIncompleteClassComponent(clazz, processing: VNode, nextProps: object): VNode | null {
mountInstance(clazz, processing, nextProps);
return createChildren(clazz, processing);
}

View File

@ -4,9 +4,8 @@ import { isSame } from '../utils/compare';
import { ClassComponent, ContextProvider } from '../vnode/VNodeTags'; import { ClassComponent, ContextProvider } from '../vnode/VNodeTags';
import { pushForceUpdate } from '../UpdateHandler'; import { pushForceUpdate } from '../UpdateHandler';
import { import {
getContextChangeCtx, resetContext,
resetContextCtx, setContext,
setContextCtx,
} from '../ContextSaver'; } from '../ContextSaver';
import { travelVNodeTree } from '../vnode/VNodeUtils'; import { travelVNodeTree } from '../vnode/VNodeUtils';
import { launchUpdateFromVNode } from '../TreeBuilder'; import { launchUpdateFromVNode } from '../TreeBuilder';
@ -75,14 +74,14 @@ function captureContextProvider(processing: VNode): VNode | null {
const newCtx = newProps.value; const newCtx = newProps.value;
// 更新processing的context值为newProps.value // 更新processing的context值为newProps.value
setContextCtx(processing, newCtx); setContext(processing, newCtx);
if (oldProps !== null) { if (oldProps !== null) {
const oldCtx = oldProps.value; const oldCtx = oldProps.value;
const isSameContext = isSame(oldCtx, newCtx); const isSameContext = isSame(oldCtx, newCtx);
if (isSameContext) { if (isSameContext) {
// context没有改变复用 // context没有改变复用
if (oldProps.children === newProps.children && !getContextChangeCtx()) { if (oldProps.children === newProps.children) {
return onlyUpdateChildVNodes(processing); return onlyUpdateChildVNodes(processing);
} }
} else { } else {
@ -101,6 +100,6 @@ export function captureRender(processing: VNode): VNode | null {
} }
export function bubbleRender(processing: VNode) { export function bubbleRender(processing: VNode) {
resetContextCtx(processing); resetContext(processing);
} }

View File

@ -1,8 +1,8 @@
import type {VNode} from '../Types'; import type {VNode} from '../Types';
import {captureRender as funCaptureRender} from './FunctionComponent'; import {captureRender as funCaptureRender} from './FunctionComponent';
export function captureRender(processing: VNode, shouldUpdate?: boolean): VNode | null { export function captureRender(processing: VNode): VNode | null {
return funCaptureRender(processing, shouldUpdate); return funCaptureRender(processing);
} }
export function bubbleRender() {} export function bubbleRender() {}

View File

@ -2,10 +2,9 @@ import type { VNode } from '../Types';
import { mergeDefaultProps } from './LazyComponent'; import { mergeDefaultProps } from './LazyComponent';
import { resetDepContexts } from '../components/context/Context'; import { resetDepContexts } from '../components/context/Context';
import { exeFunctionHook } from '../hooks/HookMain'; import { runFunctionWithHooks } from '../hooks/HookMain';
import { ForwardRef } from '../vnode/VNodeTags'; import { ForwardRef } from '../vnode/VNodeTags';
import { FlagUtils, Update } from '../vnode/VNodeFlags'; import { FlagUtils, Update } from '../vnode/VNodeFlags';
import { getContextChangeCtx } from '../ContextSaver';
import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator'; import { onlyUpdateChildVNodes } from '../vnode/VNodeCreator';
import { createChildrenByDiff } from '../diff/nodeDiffComparator'; import { createChildrenByDiff } from '../diff/nodeDiffComparator';
@ -16,28 +15,10 @@ export function bubbleRender() {
} }
// 判断children是否可以复用 // 判断children是否可以复用
function checkIfCanReuseChildren(processing: VNode, shouldUpdate?: boolean) { function checkIfCanReuseChildren(processing: VNode) {
let isCanReuse = true; return !processing.isCreated &&
processing.oldProps === processing.props &&
if (!processing.isCreated) { !processing.isDepContextChange;
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;
} }
export function setStateChange(isUpdate) { export function setStateChange(isUpdate) {
@ -52,7 +33,6 @@ export function captureFunctionComponent(
processing: VNode, processing: VNode,
funcComp: any, funcComp: any,
nextProps: any, nextProps: any,
shouldUpdate?: boolean,
) { ) {
// 函数组件内已完成异步动作 // 函数组件内已完成异步动作
if (processing.isSuspended) { if (processing.isSuspended) {
@ -64,11 +44,11 @@ export function captureFunctionComponent(
} }
resetDepContexts(processing); resetDepContexts(processing);
const isCanReuse = checkIfCanReuseChildren(processing, shouldUpdate); const isCanReuse = checkIfCanReuseChildren(processing);
// 在执行exeFunctionHook前先设置stateChange为false // 在执行exeFunctionHook前先设置stateChange为false
setStateChange(false); setStateChange(false);
const newElements = exeFunctionHook( const newElements = runFunctionWithHooks(
processing.tag === ForwardRef ? funcComp.render : funcComp, processing.tag === ForwardRef ? funcComp.render : funcComp,
nextProps, nextProps,
processing.tag === ForwardRef ? processing.ref : undefined, processing.tag === ForwardRef ? processing.ref : undefined,
@ -76,17 +56,19 @@ export function captureFunctionComponent(
); );
// 这里需要判断是否可以复用因为函数组件比起其他组价多了context和stateChange两个因素 // 这里需要判断是否可以复用因为函数组件比起其他组价多了context和stateChange两个因素
if (isCanReuse && !isStateChange()) { if (isCanReuse && !isStateChange() && !processing.isStoreChange) {
FlagUtils.removeFlag(processing, Update); FlagUtils.removeFlag(processing, Update);
return onlyUpdateChildVNodes(processing); return onlyUpdateChildVNodes(processing);
} }
processing.isStoreChange = false;
processing.child = createChildrenByDiff(processing, processing.child, newElements, !processing.isCreated); processing.child = createChildrenByDiff(processing, processing.child, newElements, !processing.isCreated);
return processing.child; return processing.child;
} }
export function captureRender(processing: VNode, shouldUpdate?: boolean): VNode | null { export function captureRender(processing: VNode): VNode | null {
const Component = processing.type; const Component = processing.type;
const unresolvedProps = processing.props; const unresolvedProps = processing.props;
const resolvedProps = const resolvedProps =
@ -98,7 +80,6 @@ export function captureRender(processing: VNode, shouldUpdate?: boolean): VNode
processing, processing,
Component, Component,
resolvedProps, resolvedProps,
shouldUpdate,
); );
} }

View File

@ -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) {}

View File

@ -4,14 +4,13 @@ import {FlagUtils, Interrupted} from '../vnode/VNodeFlags';
import { onlyUpdateChildVNodes, updateVNode, createFragmentVNode } from '../vnode/VNodeCreator'; import { onlyUpdateChildVNodes, updateVNode, createFragmentVNode } from '../vnode/VNodeCreator';
import { import {
ClassComponent, ClassComponent,
ForwardRef,
FunctionComponent, FunctionComponent,
IncompleteClassComponent,
SuspenseComponent, SuspenseComponent,
} from '../vnode/VNodeTags'; } from '../vnode/VNodeTags';
import { pushForceUpdate } from '../UpdateHandler'; import { pushForceUpdate } from '../UpdateHandler';
import { launchUpdateFromVNode, tryRenderFromRoot } from '../TreeBuilder'; import { launchUpdateFromVNode, tryRenderFromRoot } from '../TreeBuilder';
import { updateShouldUpdateOfTree } from '../vnode/VNodeShouldUpdate'; import { updateShouldUpdateOfTree } from '../vnode/VNodeShouldUpdate';
import {getContextChangeCtx} from '../ContextSaver';
import { markVNodePath } from '../utils/vNodePath'; import { markVNodePath } from '../utils/vNodePath';
export enum SuspenseChildStatus { export enum SuspenseChildStatus {
@ -47,7 +46,7 @@ function createFallback(processing: VNode, fallbackChildren) {
fallbackFragment.eIndex = 1; fallbackFragment.eIndex = 1;
fallbackFragment.cIndex = 1; fallbackFragment.cIndex = 1;
markVNodePath(fallbackFragment); markVNodePath(fallbackFragment);
processing.suspenseChildStatus = SuspenseChildStatus.ShowFallback; processing.suspenseState.childStatus = SuspenseChildStatus.ShowFallback;
return fallbackFragment; return fallbackFragment;
} }
@ -71,7 +70,7 @@ function createSuspenseChildren(processing: VNode, newChildren) {
processing.dirtyNodes = [oldFallbackFragment]; processing.dirtyNodes = [oldFallbackFragment];
} }
// SuspenseComponent 中使用 // SuspenseComponent 中使用
processing.suspenseChildStatus = SuspenseChildStatus.ShowChild; processing.suspenseState.childStatus = SuspenseChildStatus.ShowChild;
} else { } else {
childFragment = createFragmentVNode(null, newChildren); childFragment = createFragmentVNode(null, newChildren);
} }
@ -80,7 +79,7 @@ function createSuspenseChildren(processing: VNode, newChildren) {
childFragment.cIndex = 0; childFragment.cIndex = 0;
markVNodePath(childFragment); markVNodePath(childFragment);
processing.child = childFragment; processing.child = childFragment;
processing.promiseResolve = false; processing.suspenseState.promiseResolved = false;
return processing.child; return processing.child;
} }
@ -88,10 +87,10 @@ export function captureSuspenseComponent(processing: VNode) {
const nextProps = processing.props; const nextProps = processing.props;
// suspense被捕获后需要展示fallback // suspense被捕获后需要展示fallback
const showFallback = processing.suspenseDidCapture; const showFallback = processing.suspenseState.didCapture;
if (showFallback) { if (showFallback) {
processing.suspenseDidCapture = false; processing.suspenseState.didCapture = false;
const nextFallbackChildren = nextProps.fallback; const nextFallbackChildren = nextProps.fallback;
return createFallback(processing, nextFallbackChildren); return createFallback(processing, nextFallbackChildren);
} else { } else {
@ -104,12 +103,12 @@ function updateFallback(processing: VNode): Array<VNode> | VNode | null {
const childFragment: VNode | null = processing.child; const childFragment: VNode | null = processing.child;
if (childFragment?.childShouldUpdate) { if (childFragment?.childShouldUpdate) {
if (processing.promiseResolve) { if (processing.suspenseState.promiseResolved) {
// promise已完成展示promise返回的新节点 // promise已完成展示promise返回的新节点
return captureSuspenseComponent(processing); return captureSuspenseComponent(processing);
} else { } else {
// promise未完成继续显示fallback不需要继续刷新子节点 // promise未完成继续显示fallback不需要继续刷新子节点
const fallbackFragment: VNode = processing.child.next; const fallbackFragment: VNode = processing.child!.next!;
childFragment.childShouldUpdate = false; childFragment.childShouldUpdate = false;
fallbackFragment.childShouldUpdate = false; fallbackFragment.childShouldUpdate = false;
return null; return null;
@ -130,10 +129,9 @@ export function captureRender(processing: VNode, shouldUpdate: boolean): Array<V
if ( if (
!processing.isCreated && !processing.isCreated &&
processing.oldProps === processing.props && processing.oldProps === processing.props &&
!getContextChangeCtx() &&
!shouldUpdate !shouldUpdate
) { ) {
if (processing.suspenseChildStatus === SuspenseChildStatus.ShowFallback) { if (processing.suspenseState.childStatus === SuspenseChildStatus.ShowFallback) {
// 当显示fallback时suspense的子组件要更新 // 当显示fallback时suspense的子组件要更新
return updateFallback(processing); return updateFallback(processing);
} }
@ -144,8 +142,9 @@ export function captureRender(processing: VNode, shouldUpdate: boolean): Array<V
} }
export function bubbleRender(processing: VNode) { export function bubbleRender(processing: VNode) {
if (processing.suspenseChildStatus === SuspenseChildStatus.ShowFallback const { childStatus, oldChildStatus } = processing.suspenseState;
|| (!processing.isCreated && processing.oldSuspenseChildStatus === SuspenseChildStatus.ShowFallback) if (childStatus === SuspenseChildStatus.ShowFallback
|| (!processing.isCreated && oldChildStatus === SuspenseChildStatus.ShowFallback)
) { ) {
FlagUtils.markUpdate(processing); FlagUtils.markUpdate(processing);
} }
@ -154,22 +153,21 @@ export function bubbleRender(processing: VNode) {
} }
function canCapturePromise(vNode: VNode | null): boolean { function canCapturePromise(vNode: VNode | null): boolean {
return vNode?.suspenseChildStatus !== SuspenseChildStatus.ShowFallback && vNode?.props.fallback !== undefined; return vNode?.suspenseState.childStatus !== SuspenseChildStatus.ShowFallback && vNode?.props.fallback !== undefined;
} }
// 处理Suspense子组件抛出的promise // 处理Suspense子组件抛出的promise
export function handleSuspenseChildThrowError(parent: VNode, processing: VNode, error: any): boolean { export function handleSuspenseChildThrowError(parent: VNode, processing: VNode, promise: PromiseType<any>): boolean {
let vNode = parent; let vNode = parent;
// 向上找到最近的不在fallback状态的Suspense并触发重新渲染 // 向上找到最近的不在fallback状态的Suspense并触发重新渲染
do { do {
if (vNode.tag === SuspenseComponent && canCapturePromise(vNode)) { if (vNode.tag === SuspenseComponent && canCapturePromise(vNode)) {
if (vNode.suspensePromises === null) { if (vNode.suspenseState.promiseSet === null) {
vNode.suspensePromises = new Set(); vNode.suspenseState.promiseSet = new Set();
} }
vNode.suspensePromises.add(error); vNode.suspenseState.promiseSet.add(promise);
processing.suspenseChildThrow = true;
// 移除生命周期flag 和 中断flag // 移除生命周期flag 和 中断flag
FlagUtils.removeLifecycleEffectFlags(processing); FlagUtils.removeLifecycleEffectFlags(processing);
@ -178,7 +176,7 @@ export function handleSuspenseChildThrowError(parent: VNode, processing: VNode,
if (processing.tag === ClassComponent) { if (processing.tag === ClassComponent) {
if (processing.isCreated) { if (processing.isCreated) {
// 渲染类组件场景要标志未完成否则会触发componentWillUnmount // 渲染类组件场景要标志未完成否则会触发componentWillUnmount
processing.tag = IncompleteClassComponent; processing.isSuspended = true;
} else { } else {
// 类组件更新标记强制更新否则被memo等优化跳过 // 类组件更新标记强制更新否则被memo等优化跳过
pushForceUpdate(processing); 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; processing.isSuspended = true;
} }
// 应该抛出promise未完成更新标志待更新 // 应该抛出promise未完成更新标志待更新
processing.shouldUpdate = true; processing.shouldUpdate = true;
vNode.suspenseDidCapture = true; vNode.suspenseState.didCapture = true;
launchUpdateFromVNode(vNode); launchUpdateFromVNode(vNode);
return true; return true;
@ -211,7 +209,7 @@ function resolvePromise(suspenseVNode: VNode, promise: PromiseType<any>) {
if (promiseCache !== null) { if (promiseCache !== null) {
promiseCache.delete(promise); promiseCache.delete(promise);
} }
suspenseVNode.promiseResolve = true; suspenseVNode.suspenseState.promiseResolved = true;
const root = updateShouldUpdateOfTree(suspenseVNode); const root = updateShouldUpdateOfTree(suspenseVNode);
if (root !== null) { if (root !== null) {
tryRenderFromRoot(root); tryRenderFromRoot(root);
@ -220,9 +218,9 @@ function resolvePromise(suspenseVNode: VNode, promise: PromiseType<any>) {
// 对于每个promise添加一个侦听器以便当它resolve时重新渲染 // 对于每个promise添加一个侦听器以便当它resolve时重新渲染
export function listenToPromise(suspenseVNode: VNode) { export function listenToPromise(suspenseVNode: VNode) {
const promises: Set<PromiseType<any>> | null = suspenseVNode.suspensePromises; const promises: Set<PromiseType<any>> | null = suspenseVNode.suspenseState.promiseSet;
if (promises !== null) { if (promises !== null) {
suspenseVNode.suspensePromises = null; suspenseVNode.suspenseState.promiseSet = null;
// 记录已经监听的 promise // 记录已经监听的 promise
let promiseCache = suspenseVNode.realNode; let promiseCache = suspenseVNode.realNode;

View File

@ -9,7 +9,6 @@ import * as DomComponentRender from './DomComponent';
import * as DomPortalRender from './DomPortal'; import * as DomPortalRender from './DomPortal';
import * as TreeRootRender from './TreeRoot'; import * as TreeRootRender from './TreeRoot';
import * as DomTextRender from './DomText'; import * as DomTextRender from './DomText';
import * as IncompleteClassComponentRender from './IncompleteClassComponent';
import * as LazyComponentRender from './LazyComponent'; import * as LazyComponentRender from './LazyComponent';
import * as MemoComponentRender from './MemoComponent'; import * as MemoComponentRender from './MemoComponent';
import * as SuspenseComponentRender from './SuspenseComponent'; import * as SuspenseComponentRender from './SuspenseComponent';
@ -25,15 +24,14 @@ import {
DomPortal, DomPortal,
TreeRoot, TreeRoot,
DomText, DomText,
IncompleteClassComponent,
LazyComponent, LazyComponent,
MemoComponent, MemoComponent,
SuspenseComponent, SuspenseComponent,
} from '../vnode/VNodeTags'; } from '../vnode/VNodeTags';
export { export {
BaseComponentRender BaseComponentRender,
} };
export default { export default {
[ClassComponent]: ClassComponentRender, [ClassComponent]: ClassComponentRender,
@ -46,8 +44,7 @@ export default {
[DomPortal]: DomPortalRender, [DomPortal]: DomPortalRender,
[TreeRoot]: TreeRootRender, [TreeRoot]: TreeRootRender,
[DomText]: DomTextRender, [DomText]: DomTextRender,
[IncompleteClassComponent]: IncompleteClassComponentRender,
[LazyComponent]: LazyComponentRender, [LazyComponent]: LazyComponentRender,
[MemoComponent]: MemoComponentRender, [MemoComponent]: MemoComponentRender,
[SuspenseComponent]: SuspenseComponentRender, [SuspenseComponent]: SuspenseComponentRender,
} };

View File

@ -32,7 +32,7 @@ import {
callEffectRemove, callEffectRemove,
callUseEffects, callUseEffects,
callUseLayoutEffectCreate, callUseLayoutEffectCreate,
callUseLayoutEffectRemove callUseLayoutEffectRemove,
} from './HookEffectHandler'; } from './HookEffectHandler';
import { handleSubmitError } from '../ErrorHandler'; import { handleSubmitError } from '../ErrorHandler';
import { import {
@ -192,7 +192,8 @@ function unmountVNode(vNode: VNode): void {
const instance = vNode.realNode; const instance = vNode.realNode;
// 当constructor中抛出异常时instance会是null这里判断一下instance是否为空 // 当constructor中抛出异常时instance会是null这里判断一下instance是否为空
if (instance && typeof instance.componentWillUnmount === 'function') { // suspense打断时不需要触发WillUnmount
if (instance && typeof instance.componentWillUnmount === 'function' && !vNode.isSuspended) {
callComponentWillUnmount(vNode, instance); callComponentWillUnmount(vNode, instance);
} }
break; break;
@ -399,9 +400,9 @@ function submitUpdate(vNode: VNode): void {
} }
function submitSuspenseComponent(vNode: VNode) { function submitSuspenseComponent(vNode: VNode) {
const suspenseChildStatus = vNode.suspenseChildStatus; const { childStatus } = vNode.suspenseState;
if (suspenseChildStatus !== SuspenseChildStatus.Init) { if (childStatus !== SuspenseChildStatus.Init) {
hideOrUnhideAllChildren(vNode.child, suspenseChildStatus === SuspenseChildStatus.ShowFallback); hideOrUnhideAllChildren(vNode.child, childStatus === SuspenseChildStatus.ShowFallback);
} }
} }

View File

@ -1,9 +1,24 @@
/** /**
* DOM结构体 * 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 { VNodeTag } from './VNodeTags';
import type { RefType, ContextType } from '../Types'; import type { RefType, ContextType, SuspenseState } from '../Types';
import type { Hook } from '../hooks/HookType'; import type { Hook } from '../hooks/HookType';
import { InitFlag } from './VNodeFlags'; import { InitFlag } from './VNodeFlags';
@ -24,7 +39,6 @@ export class VNode {
ref: RefType | ((handle: any) => void) | null = null; // 包裹一个函数submit阶段使用比如将外部useRef生成的对象赋值到ref上 ref: RefType | ((handle: any) => void) | null = null; // 包裹一个函数submit阶段使用比如将外部useRef生成的对象赋值到ref上
oldProps: any = null; oldProps: any = null;
suspensePromises: any; // suspense组件的promise列表
changeList: any; // DOM的变更列表 changeList: any; // DOM的变更列表
effectList: any[] | null; // useEffect 的更新数组 effectList: any[] | null; // useEffect 的更新数组
updates: any[] | null; // TreeRoot和ClassComponent使用的更新数组 updates: any[] | null; // TreeRoot和ClassComponent使用的更新数组
@ -33,7 +47,6 @@ export class VNode {
isSuspended = false; // 是否被suspense打断更新 isSuspended = false; // 是否被suspense打断更新
state: any; // ClassComponent和TreeRoot的状态 state: any; // ClassComponent和TreeRoot的状态
hooks: Array<Hook<any, any>> | null; // 保存hook hooks: Array<Hook<any, any>> | null; // 保存hook
suspenseChildStatus = ''; // Suspense的Children是否显示
depContexts: Array<ContextType<any>> | null; // FunctionComponent和ClassComponent对context的依赖列表 depContexts: Array<ContextType<any>> | null; // FunctionComponent和ClassComponent对context的依赖列表
isDepContextChange: boolean; // context是否变更 isDepContextChange: boolean; // context是否变更
dirtyNodes: Array<VNode> | null = null; // 需要改动的节点数组 dirtyNodes: Array<VNode> | null = null; // 需要改动的节点数组
@ -42,7 +55,7 @@ export class VNode {
task: any; task: any;
// 使用这个变量来记录修改前的值,用于恢复。 // 使用这个变量来记录修改前的值,用于恢复。
contexts: any; context: any;
// 因为LazyComponent会修改tag和type属性为了能识别增加一个属性 // 因为LazyComponent会修改tag和type属性为了能识别增加一个属性
isLazyComponent: boolean; isLazyComponent: boolean;
@ -55,17 +68,20 @@ export class VNode {
oldHooks: Array<Hook<any, any>> | null; // 保存上一次执行的hook oldHooks: Array<Hook<any, any>> | null; // 保存上一次执行的hook
oldState: any; oldState: any;
oldRef: RefType | ((handle: any) => void) | null = null; oldRef: RefType | ((handle: any) => void) | null = null;
suspenseChildThrow: boolean;
oldSuspenseChildStatus: string; // 上一次Suspense的Children是否显示
oldChild: VNode | null = null; oldChild: VNode | null = null;
suspenseDidCapture: boolean; // suspense是否捕获了异常
promiseResolve: boolean; // suspense的promise是否resolve promiseResolve: boolean; // suspense的promise是否resolve
suspenseState: SuspenseState;
path = ''; // 保存从根到本节点的路径 path = ''; // 保存从根到本节点的路径
toUpdateNodes: Set<VNode> | null; // 保存要更新的节点 toUpdateNodes: Set<VNode> | null; // 保存要更新的节点
belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode处理ref的时候使用 belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode处理ref的时候使用
// 状态管理器使用
isStoreChange: boolean;
functionToObserver: FunctionToObserver | null; // 记录这个函数组件依赖哪些Observer
constructor(tag: VNodeTag, props: any, key: null | string, realNode) { constructor(tag: VNodeTag, props: any, key: null | string, realNode) {
this.tag = tag; // 对应组件的类型比如ClassComponent等 this.tag = tag; // 对应组件的类型比如ClassComponent等
this.key = key; this.key = key;
@ -81,7 +97,7 @@ export class VNode {
this.stateCallbacks = null; this.stateCallbacks = null;
this.state = null; this.state = null;
this.oldState = null; this.oldState = null;
this.contexts = null; this.context = null;
break; break;
case FunctionComponent: case FunctionComponent:
this.realNode = null; this.realNode = null;
@ -90,6 +106,8 @@ export class VNode {
this.depContexts = null; this.depContexts = null;
this.isDepContextChange = false; this.isDepContextChange = false;
this.oldHooks = null; this.oldHooks = null;
this.isStoreChange = false;
this.functionToObserver = null;
break; break;
case ClassComponent: case ClassComponent:
this.realNode = null; this.realNode = null;
@ -100,30 +118,32 @@ export class VNode {
this.depContexts = null; this.depContexts = null;
this.isDepContextChange = false; this.isDepContextChange = false;
this.oldState = null; this.oldState = null;
this.contexts = null; this.context = null;
break; break;
case DomPortal: case DomPortal:
this.realNode = null; this.realNode = null;
this.contexts = null; this.context = null;
break; break;
case DomComponent: case DomComponent:
this.realNode = null; this.realNode = null;
this.changeList = null; this.changeList = null;
this.contexts = null; this.context = null;
break; break;
case DomText: case DomText:
this.realNode = null; this.realNode = null;
break; break;
case SuspenseComponent: case SuspenseComponent:
this.realNode = null; this.realNode = null;
this.suspensePromises = null; this.suspenseState = {
this.suspenseChildThrow = false; promiseSet: null,
this.suspenseDidCapture = false; didCapture: false,
this.promiseResolve = false; promiseResolved: false,
this.oldSuspenseChildStatus = ''; oldChildStatus: '',
childStatus: ''
};
break; break;
case ContextProvider: case ContextProvider:
this.contexts = null; this.context = null;
break; break;
case MemoComponent: case MemoComponent:
this.effectList = null; this.effectList = null;
@ -143,8 +163,6 @@ export class VNode {
break; break;
case Profiler: case Profiler:
break; break;
case IncompleteClassComponent:
break;
} }
} }
} }

View File

@ -38,9 +38,9 @@ const typeMap = {
[TYPE_LAZY]: LazyComponent, [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); return new VNode(tag, vNodeProps, key, realNode);
}; }
function isClassComponent(comp: Function) { function isClassComponent(comp: Function) {
// 如果使用 getPrototypeOf 方法获取构造函数,不能兼容业务组组件继承组件的使用方式,会误认为是函数组件 // 如果使用 getPrototypeOf 方法获取构造函数,不能兼容业务组组件继承组件的使用方式,会误认为是函数组件
@ -66,7 +66,7 @@ export function updateVNode(vNode: VNode, vNodeProps?: any): VNode {
} }
if (vNode.tag === SuspenseComponent) { if (vNode.tag === SuspenseComponent) {
vNode.oldSuspenseChildStatus = vNode.suspenseChildStatus; vNode.suspenseState.oldChildStatus = vNode.suspenseState.childStatus;
vNode.oldChild = vNode.child; vNode.oldChild = vNode.child;
} }
@ -201,7 +201,7 @@ export function onlyUpdateChildVNodes(processing: VNode): VNode | null {
sibling = sibling.next; sibling = sibling.next;
} }
} }
} };
putChildrenIntoQueue(processing.child); putChildrenIntoQueue(processing.child);
@ -210,7 +210,7 @@ export function onlyUpdateChildVNodes(processing: VNode): VNode | null {
markVNodePath(vNode); markVNodePath(vNode);
putChildrenIntoQueue(vNode) putChildrenIntoQueue(vNode);
} }
} }
// 子树无需工作 // 子树无需工作

View File

@ -53,7 +53,7 @@ export function setParentsChildShouldUpdate(parent: VNode | null) {
// 设置节点的所有父节点的childShouldUpdate // 设置节点的所有父节点的childShouldUpdate
export function updateParentsChildShouldUpdate(vNode: VNode) { export function updateParentsChildShouldUpdate(vNode: VNode) {
let node = vNode.parent; let node = vNode.parent;
let isShouldUpdate = vNode.shouldUpdate || vNode.childShouldUpdate; const isShouldUpdate = vNode.shouldUpdate || vNode.childShouldUpdate;
if (isShouldUpdate) { // 开始节点是shouldUpdate或childShouldUpdate if (isShouldUpdate) { // 开始节点是shouldUpdate或childShouldUpdate
// 更新从当前节点到根节点的childShouldUpdate为true // 更新从当前节点到根节点的childShouldUpdate为true

View File

@ -82,7 +82,7 @@ export function clearVNode(vNode: VNode) {
vNode.hooks = null; vNode.hooks = null;
vNode.props = null; vNode.props = null;
vNode.parent = null; vNode.parent = null;
vNode.suspensePromises = null; vNode.suspenseState = null;
vNode.changeList = null; vNode.changeList = null;
vNode.effectList = null; vNode.effectList = null;
vNode.updates = null; vNode.updates = null;

View File

@ -1,9 +1,7 @@
import * as Horizon from '@cloudsop/horizon/index.ts'; import * as Horizon from '@cloudsop/horizon/index.ts';
import { act } from '../../jest/customMatcher';
describe('useContext Hook Test', () => { describe('useContext Hook Test', () => {
const { useState, useContext } = Horizon; const { useState, useContext, createContext, act, unmountComponentAtNode } = Horizon;
const { unmountComponentAtNode } = Horizon;
it('简单使用useContext', () => { it('简单使用useContext', () => {
const LanguageTypes = { const LanguageTypes = {
@ -47,4 +45,38 @@ describe('useContext Hook Test', () => {
act(() => setValue(LanguageTypes.JAVASCRIPT)); act(() => setValue(LanguageTypes.JAVASCRIPT));
expect(container.querySelector('p').innerHTML).toBe('JavaScript'); expect(container.querySelector('p').innerHTML).toBe('JavaScript');
}); });
it('更新后useContext仍能获取到context', () => {
const Context = createContext({});
const ref = Horizon.createRef();
function App() {
return (
<Context.Provider
value={{
text: 'context',
}}
>
<Child />
</Context.Provider>
);
}
let update;
function Child() {
const context = useContext(Context);
const [_, setState] = useState({});
update = () => setState({});
return <div ref={ref}>{context.text}</div>;
}
Horizon.render(<App />, container);
expect(ref.current.innerHTML).toBe('context');
update();
expect(ref.current.innerHTML).toBe('context');
});
}); });

View File

@ -1,7 +1,6 @@
import * as Horizon from '@cloudsop/horizon/index.ts'; import * as Horizon from '@cloudsop/horizon/index.ts';
import * as LogUtils from '../../jest/logUtils'; import * as LogUtils from '../../jest/logUtils';
import { act } from '../../jest/customMatcher'; import { Text } from '../../jest/commonComponents';
import Text from '../../jest/Text';
describe('useEffect Hook Test', () => { describe('useEffect Hook Test', () => {
const { const {
@ -9,7 +8,8 @@ describe('useEffect Hook Test', () => {
useLayoutEffect, useLayoutEffect,
useState, useState,
memo, memo,
forwardRef forwardRef,
act,
} = Horizon; } = Horizon;
it('简单使用useEffect', () => { it('简单使用useEffect', () => {

View File

@ -1,13 +1,13 @@
import * as Horizon from '@cloudsop/horizon/index.ts'; import * as Horizon from '@cloudsop/horizon/index.ts';
import * as LogUtils from '../../jest/logUtils'; import * as LogUtils from '../../jest/logUtils';
import { act } from '../../jest/customMatcher'; import { Text } from '../../jest/commonComponents';
import Text from '../../jest/Text';
describe('useImperativeHandle Hook Test', () => { describe('useImperativeHandle Hook Test', () => {
const { const {
useState, useState,
useImperativeHandle, useImperativeHandle,
forwardRef forwardRef,
act,
} = Horizon; } = Horizon;
const { unmountComponentAtNode } = Horizon; const { unmountComponentAtNode } = Horizon;

View File

@ -1,13 +1,13 @@
import * as Horizon from '@cloudsop/horizon/index.ts'; import * as Horizon from '@cloudsop/horizon/index.ts';
import * as LogUtils from '../../jest/logUtils'; import * as LogUtils from '../../jest/logUtils';
import { act } from '../../jest/customMatcher'; import { Text } from '../../jest/commonComponents';
import Text from '../../jest/Text';
describe('useLayoutEffect Hook Test', () => { describe('useLayoutEffect Hook Test', () => {
const { const {
useState, useState,
useEffect, useEffect,
useLayoutEffect useLayoutEffect,
act,
} = Horizon; } = Horizon;
it('简单使用useLayoutEffect', () => { it('简单使用useLayoutEffect', () => {

View File

@ -1,6 +1,6 @@
import * as Horizon from '@cloudsop/horizon/index.ts'; import * as Horizon from '@cloudsop/horizon/index.ts';
import * as LogUtils from '../../jest/logUtils'; import * as LogUtils from '../../jest/logUtils';
import Text from '../../jest/Text'; import { Text } from '../../jest/commonComponents';
describe('useMemo Hook Test', () => { describe('useMemo Hook Test', () => {
const { useMemo, useState } = Horizon; const { useMemo, useState } = Horizon;

View File

@ -1,6 +1,6 @@
import * as Horizon from '@cloudsop/horizon/index.ts'; import * as Horizon from '@cloudsop/horizon/index.ts';
import * as LogUtils from '../../jest/logUtils'; import * as LogUtils from '../../jest/logUtils';
import Text from '../../jest/Text'; import { Text } from '../../jest/commonComponents';
describe('useRef Hook Test', () => { describe('useRef Hook Test', () => {
const { useState, useRef } = Horizon; const { useState, useRef } = Horizon;

View File

@ -1,14 +1,14 @@
import * as Horizon from '@cloudsop/horizon/index.ts'; import * as Horizon from '@cloudsop/horizon/index.ts';
import * as LogUtils from '../../jest/logUtils'; import * as LogUtils from '../../jest/logUtils';
import { act } from '../../jest/customMatcher'; import { Text } from '../../jest/commonComponents';
import Text from '../../jest/Text';
describe('useState Hook Test', () => { describe('useState Hook Test', () => {
const { const {
useState, useState,
forwardRef, forwardRef,
useImperativeHandle, useImperativeHandle,
memo memo,
act,
} = Horizon; } = Horizon;
it('简单使用useState', () => { it('简单使用useState', () => {

View File

@ -1,6 +1,5 @@
import * as Horizon from '@cloudsop/horizon/index.ts'; import * as Horizon from '@cloudsop/horizon/index.ts';
import * as LogUtils from '../jest/logUtils'; import * as LogUtils from '../jest/logUtils';
import { act } from '../jest/customMatcher';
describe('合成焦点事件', () => { describe('合成焦点事件', () => {

View File

@ -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 <p>{props.text}</p>;
};
export default Text;

View File

@ -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 (
<div>
<Parent>
<Child />
</Parent>
</div>
);
}
export const Text = (props) => {
LogUtils.log(props.text);
return <p id={props.id}>{props.text}</p>;
}

View File

@ -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
};

View File

@ -17,7 +17,27 @@ global.afterEach(() => {
LogUtils.clear(); 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感知自定义匹配器 // 使Jest感知自定义匹配器
expect.extend({ expect.extend({
...require('./customMatcher'), toMatchValue,
}); });

View File

@ -9,7 +9,7 @@ export const stopBubbleOrCapture = (e, value) => {
export const getEventListeners = (dom) => { export const getEventListeners = (dom) => {
let ret = true let ret = true
let keyArray = []; let keyArray = [];
for (var key in dom) { for (let key in dom) {
keyArray.push(key); keyArray.push(key);
} }
try { try {
@ -24,3 +24,10 @@ export const getEventListeners = (dom) => {
} }
return ret; return ret;
}; };
export function triggerClickEvent(container, id) {
const event = new MouseEvent('click', {
bubbles: true,
});
container.querySelector(`#${id}`).dispatchEvent(event);
}