Match-id-0cbbf861f75e90b3561570fa0ba46e24a1a51c30
This commit is contained in:
commit
54235cd0e3
25
.eslintrc.js
25
.eslintrc.js
|
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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,
|
|
||||||
};
|
|
|
@ -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,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重置环境变量,为重新进行深度遍历做准备
|
// 重置环境变量,为重新进行深度遍历做准备
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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};
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
|
|
@ -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进行处理
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() {}
|
||||||
|
|
|
@ -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,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) {}
|
|
|
@ -1,17 +1,16 @@
|
||||||
import type {VNode, PromiseType} from '../Types';
|
import type { VNode, PromiseType } from '../Types';
|
||||||
|
|
||||||
import {FlagUtils, Interrupted} from '../vnode/VNodeFlags';
|
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 {
|
||||||
|
@ -101,15 +100,15 @@ export function captureSuspenseComponent(processing: VNode) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFallback(processing: VNode): Array<VNode> | VNode | null {
|
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;
|
||||||
|
|
|
@ -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,
|
||||||
}
|
};
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -329,7 +330,7 @@ function submitClear(vNode: VNode): void {
|
||||||
// 但考虑到用户可能自定义其他属性,所以采用遍历赋值的方式
|
// 但考虑到用户可能自定义其他属性,所以采用遍历赋值的方式
|
||||||
const customizeKeys = Object.keys(realNode);
|
const customizeKeys = Object.keys(realNode);
|
||||||
const keyLength = customizeKeys.length;
|
const keyLength = customizeKeys.length;
|
||||||
for(let i = 0; i < keyLength; i++) {
|
for (let i = 0; i < keyLength; i++) {
|
||||||
const key = customizeKeys[i];
|
const key = customizeKeys[i];
|
||||||
// 测试代码 mock 实例的全部可遍历属性都会被Object.keys方法读取到
|
// 测试代码 mock 实例的全部可遍历属性都会被Object.keys方法读取到
|
||||||
// children 属性被复制意味着复制了子节点,因此要排除
|
// children 属性被复制意味着复制了子节点,因此要排除
|
||||||
|
@ -351,7 +352,7 @@ function submitClear(vNode: VNode): void {
|
||||||
}
|
}
|
||||||
let clearChild = vNode.clearChild as VNode; // 上次渲染的child保存在clearChild属性中
|
let clearChild = vNode.clearChild as VNode; // 上次渲染的child保存在clearChild属性中
|
||||||
// 卸载 clearChild 和 它的兄弟节点
|
// 卸载 clearChild 和 它的兄弟节点
|
||||||
while(clearChild) {
|
while (clearChild) {
|
||||||
// 卸载子vNode,递归遍历子vNode
|
// 卸载子vNode,递归遍历子vNode
|
||||||
unmountNestedVNodes(clearChild);
|
unmountNestedVNodes(clearChild);
|
||||||
clearVNode(clearChild);
|
clearVNode(clearChild);
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 子树无需工作
|
// 子树无需工作
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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', () => {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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', () => {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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', () => {
|
||||||
|
|
|
@ -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('合成焦点事件', () => {
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
|
@ -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>;
|
||||||
|
}
|
|
@ -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
|
|
||||||
};
|
|
|
@ -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,
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue