Match-id-645585c045294627f1306a4e0d3fce1d81af07c8
This commit is contained in:
commit
418bdb38a1
|
@ -0,0 +1,147 @@
|
||||||
|
/**
|
||||||
|
* 保存与深度遍历相关的一些context。
|
||||||
|
* 在深度遍历过程中,begin阶段会修改一些全局的值,在complete阶段会恢复。
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {VNode, ContextType} from './Types';
|
||||||
|
import type {Container} from '../dom/DOMOperator';
|
||||||
|
|
||||||
|
import {getNSCtx} from '../dom/DOMOperator';
|
||||||
|
import {ContextProvider} from './vnode/VNodeTags';
|
||||||
|
|
||||||
|
// 保存的是“http://www.w3.org/1999/xhtml”或“http://www.w3.org/2000/svg”,
|
||||||
|
// 用于识别是使用document.createElement()还是使用document.createElementNS()创建DOM
|
||||||
|
const CTX_NAMESPACE = 'CTX_NAMESPACE';
|
||||||
|
|
||||||
|
// 保存的是Horizon.createContext()的值,或Provider重新设置的值
|
||||||
|
const CTX_CONTEXT = 'CTX_CONTEXT';
|
||||||
|
|
||||||
|
// 旧版context API,是否更改。
|
||||||
|
const CTX_OLD_CHANGE = 'CTX_OLD_CHANGE';
|
||||||
|
// 旧版context API,保存的是的当前组件提供给子组件使用的context。
|
||||||
|
const CTX_OLD_CONTEXT = 'CTX_OLD_CONTEXT';
|
||||||
|
// 旧版context API,保存的是的上一个提供者提供给后代组件使用的context。
|
||||||
|
const CTX_OLD_PREVIOUS_CONTEXT = 'CTX_OLD_PREVIOUS_CONTEXT';
|
||||||
|
|
||||||
|
let ctxNamespace: string = '';
|
||||||
|
|
||||||
|
let ctxOldContext: Object = {};
|
||||||
|
let ctxOldChange: Boolean = false;
|
||||||
|
let ctxOldPreviousContext: Object = {};
|
||||||
|
|
||||||
|
// capture阶段设置
|
||||||
|
function setNamespaceCtx(vNode: VNode, dom?: Container) {
|
||||||
|
const nextContext = getNSCtx(dom, ctxNamespace, vNode.type);
|
||||||
|
|
||||||
|
vNode.setContext(CTX_NAMESPACE, ctxNamespace);
|
||||||
|
ctxNamespace = nextContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bubble阶段恢复
|
||||||
|
function resetNamespaceCtx(vNode: VNode) {
|
||||||
|
ctxNamespace = vNode.getContext(CTX_NAMESPACE);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNamespaceCtx(): string {
|
||||||
|
return ctxNamespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setContextCtx<T>(providerVNode: VNode, nextValue: T) {
|
||||||
|
const context: ContextType<T> = providerVNode.type._context;
|
||||||
|
|
||||||
|
providerVNode.setContext(CTX_CONTEXT, context.value);
|
||||||
|
context.value = nextValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetContextCtx(providerVNode: VNode) {
|
||||||
|
const context: ContextType<any> = providerVNode.type._context;
|
||||||
|
|
||||||
|
context.value = providerVNode.getContext(CTX_CONTEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在局部更新时,恢复父节点的context
|
||||||
|
function recoverParentsContextCtx(vNode: VNode) {
|
||||||
|
let parent = vNode.parent;
|
||||||
|
|
||||||
|
while (parent !== null) {
|
||||||
|
if (parent.tag === ContextProvider) {
|
||||||
|
const newValue = parent.props.value;
|
||||||
|
setContextCtx(parent, newValue);
|
||||||
|
}
|
||||||
|
parent = parent.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ctxOldContext是 旧context提供者的context
|
||||||
|
function setVNodeOldContext(providerVNode: VNode, context: Object) {
|
||||||
|
providerVNode.setContext(CTX_OLD_CONTEXT, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVNodeOldContext(vNode: VNode) {
|
||||||
|
return vNode.getContext(CTX_OLD_CONTEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setOldContextCtx(providerVNode: VNode, context: Object) {
|
||||||
|
setVNodeOldContext(providerVNode, context);
|
||||||
|
ctxOldContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOldContextCtx() {
|
||||||
|
return ctxOldContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetOldContextCtx(vNode: VNode) {
|
||||||
|
ctxOldContext = getVNodeOldContext(vNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setVNodeOldPreviousContext(providerVNode: VNode, context: Object) {
|
||||||
|
providerVNode.setContext(CTX_OLD_PREVIOUS_CONTEXT, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVNodeOldPreviousContext(vNode: VNode) {
|
||||||
|
return vNode.getContext(CTX_OLD_PREVIOUS_CONTEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setOldPreviousContextCtx(context: Object) {
|
||||||
|
ctxOldPreviousContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getOldPreviousContextCtx() {
|
||||||
|
return ctxOldPreviousContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setContextChangeCtx(providerVNode: VNode, didChange: boolean) {
|
||||||
|
providerVNode.setContext(CTX_OLD_CHANGE, didChange);
|
||||||
|
ctxOldChange = didChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getContextChangeCtx() {
|
||||||
|
return ctxOldChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetContextChangeCtx(vNode: VNode) {
|
||||||
|
ctxOldChange = vNode.getContext(CTX_OLD_CHANGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
getNamespaceCtx,
|
||||||
|
resetNamespaceCtx,
|
||||||
|
setNamespaceCtx,
|
||||||
|
setContextCtx,
|
||||||
|
resetContextCtx,
|
||||||
|
recoverParentsContextCtx,
|
||||||
|
setOldContextCtx,
|
||||||
|
getOldContextCtx,
|
||||||
|
resetOldContextCtx,
|
||||||
|
setContextChangeCtx,
|
||||||
|
getContextChangeCtx,
|
||||||
|
resetContextChangeCtx,
|
||||||
|
setOldPreviousContextCtx,
|
||||||
|
getOldPreviousContextCtx,
|
||||||
|
setVNodeOldContext,
|
||||||
|
getVNodeOldContext,
|
||||||
|
setVNodeOldPreviousContext,
|
||||||
|
getVNodeOldPreviousContext,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,189 @@
|
||||||
|
/**
|
||||||
|
* 异常错误处理
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {VNode} from './Types';
|
||||||
|
import type {Update} from './UpdateHandler';
|
||||||
|
|
||||||
|
import {ClassComponent, TreeRoot} from './vnode/VNodeTags';
|
||||||
|
import {FlagUtils, Interrupted} from './vnode/VNodeFlags';
|
||||||
|
import {newUpdate, UpdateState, pushUpdate} from './UpdateHandler';
|
||||||
|
import {launchUpdateFromVNode, setBuildResultError, tryRenderRoot} from './TreeBuilder';
|
||||||
|
import {setRootThrowError} from './submit/Submit';
|
||||||
|
import {handleSuspenseChildThrowError} from './render/SuspenseComponent';
|
||||||
|
import {updateShouldUpdateOfTree} from './vnode/VNodeShouldUpdate';
|
||||||
|
|
||||||
|
// 处理capture和bubble阶段抛出的错误
|
||||||
|
export function handleRenderThrowError(
|
||||||
|
sourceVNode: VNode,
|
||||||
|
error: any,
|
||||||
|
) {
|
||||||
|
// vNode抛出了异常,标记Interrupted中断
|
||||||
|
FlagUtils.markInterrupted(sourceVNode);
|
||||||
|
// dirtyNodes 不再有效
|
||||||
|
sourceVNode.dirtyNodes = [];
|
||||||
|
|
||||||
|
// error是个promise
|
||||||
|
if (error !== null && typeof error === 'object' && typeof error.then === 'function') {
|
||||||
|
// 抛出异常的节点,向上寻找,是否有suspense组件
|
||||||
|
const foundSuspense = handleSuspenseChildThrowError(sourceVNode.parent, sourceVNode, error);
|
||||||
|
if (foundSuspense) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 抛出错误无法作为suspense内容处理(或无suspense来处理),这次当成真的错误来处理
|
||||||
|
setBuildResultError();
|
||||||
|
|
||||||
|
// 向上遍历寻找ClassComponent组件(同时也是Error Boundaries组件) 或者 TreeRoot
|
||||||
|
let vNode = sourceVNode.parent;
|
||||||
|
do {
|
||||||
|
switch (vNode.tag) {
|
||||||
|
case TreeRoot: {
|
||||||
|
vNode.shouldUpdate = true;
|
||||||
|
launchUpdateFromVNode(vNode);
|
||||||
|
handleRootError(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case ClassComponent:
|
||||||
|
const ctor = vNode.type;
|
||||||
|
const instance = vNode.realNode;
|
||||||
|
if (
|
||||||
|
!vNode.flags.DidCapture &&
|
||||||
|
(
|
||||||
|
typeof ctor.getDerivedStateFromError === 'function' ||
|
||||||
|
(instance !== null && typeof instance.componentDidCatch === 'function')
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
FlagUtils.markShouldCapture(vNode);
|
||||||
|
|
||||||
|
// Class捕捉到异常,触发一次刷新
|
||||||
|
const update = createClassErrorUpdate(vNode, error);
|
||||||
|
pushUpdate(vNode, update);
|
||||||
|
|
||||||
|
launchUpdateFromVNode(vNode);
|
||||||
|
|
||||||
|
// 有异常处理类,把抛出异常的节点的Interrupted标志去掉,继续走正常的绘制流程
|
||||||
|
FlagUtils.removeFlag(sourceVNode, Interrupted);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
vNode = vNode.parent;
|
||||||
|
} while (vNode !== null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理submit阶段的异常
|
||||||
|
export function handleSubmitError(vNode: VNode, error: any) {
|
||||||
|
if (vNode.tag === TreeRoot) {
|
||||||
|
handleRootError(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let node = vNode.parent;
|
||||||
|
// 向上遍历
|
||||||
|
while (node !== null) {
|
||||||
|
if (node.tag === TreeRoot) {
|
||||||
|
handleRootError(error);
|
||||||
|
return;
|
||||||
|
} else if (node.tag === ClassComponent) { // 只有 class 组件才可以成为错误边界组件
|
||||||
|
const ctor = node.type;
|
||||||
|
const instance = node.realNode;
|
||||||
|
if (
|
||||||
|
typeof ctor.getDerivedStateFromError === 'function' ||
|
||||||
|
typeof instance.componentDidCatch === 'function'
|
||||||
|
) {
|
||||||
|
const getDerivedStateFromError = node.type.getDerivedStateFromError;
|
||||||
|
if (typeof getDerivedStateFromError === 'function') {
|
||||||
|
// 打印错误
|
||||||
|
consoleError(error);
|
||||||
|
|
||||||
|
const retState = getDerivedStateFromError(error);
|
||||||
|
if (retState) { // 有返回值
|
||||||
|
// 触发更新
|
||||||
|
triggerUpdate(node, retState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理componentDidCatch
|
||||||
|
if (instance !== null && typeof instance.componentDidCatch === 'function') {
|
||||||
|
if (typeof getDerivedStateFromError !== 'function') { // 没有getDerivedStateFromError
|
||||||
|
// 打印错误
|
||||||
|
consoleError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.componentDidCatch(error, {
|
||||||
|
componentStack: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node = node.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createClassErrorUpdate(
|
||||||
|
vNode: VNode,
|
||||||
|
error: any,
|
||||||
|
): Update {
|
||||||
|
const update = newUpdate();
|
||||||
|
update.type = UpdateState.Error;
|
||||||
|
|
||||||
|
const getDerivedStateFromError = vNode.type.getDerivedStateFromError;
|
||||||
|
if (typeof getDerivedStateFromError === 'function') {
|
||||||
|
update.content = () => {
|
||||||
|
consoleError(error);
|
||||||
|
return getDerivedStateFromError(error);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const inst = vNode.realNode;
|
||||||
|
if (inst !== null && typeof inst.componentDidCatch === 'function') {
|
||||||
|
update.callback = function callback() {
|
||||||
|
if (typeof getDerivedStateFromError !== 'function') {
|
||||||
|
// 打印错误
|
||||||
|
consoleError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
this.componentDidCatch(error, {
|
||||||
|
componentStack: '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return update;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增一个update,并且触发调度
|
||||||
|
function triggerUpdate(vNode, state) {
|
||||||
|
const update = newUpdate();
|
||||||
|
update.content = state;
|
||||||
|
pushUpdate(vNode, update);
|
||||||
|
|
||||||
|
const root = updateShouldUpdateOfTree(vNode);
|
||||||
|
if (root !== null) {
|
||||||
|
tryRenderRoot(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRootError(
|
||||||
|
error: any,
|
||||||
|
) {
|
||||||
|
// 注意:如果根节点抛出错误,不会销毁整棵树,只打印日志,抛出异常。
|
||||||
|
setRootThrowError(error);
|
||||||
|
consoleError(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
function consoleError(error: any): void {
|
||||||
|
if (isDev) {
|
||||||
|
// 只打印message为了让测试用例能pass
|
||||||
|
console['error']('The codes throw the error: ' + error.message);
|
||||||
|
} else {
|
||||||
|
console['error'](error);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
|
||||||
|
export const ByAsync = 'BY_ASYNC';
|
||||||
|
export const BySync = 'BY_SYNC';
|
||||||
|
export const InRender = 'IN_RENDER';
|
||||||
|
|
||||||
|
type RenderMode = typeof ByAsync | typeof BySync | typeof InRender;
|
||||||
|
|
||||||
|
// 当前执行阶段标记
|
||||||
|
let executeMode = {
|
||||||
|
[ByAsync]: false,
|
||||||
|
[BySync]: false,
|
||||||
|
[InRender]: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function changeMode(mode: RenderMode, state = true) {
|
||||||
|
executeMode[mode] = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkMode(mode: RenderMode) {
|
||||||
|
return executeMode[mode];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isExecuting() {
|
||||||
|
return executeMode[ByAsync] || executeMode[BySync] || executeMode[InRender];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function copyExecuteMode() {
|
||||||
|
return {...executeMode};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setExecuteMode(mode: typeof executeMode) {
|
||||||
|
executeMode = mode;
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
import type {VNode} from './Types';
|
||||||
|
import type {Update} from './UpdateHandler';
|
||||||
|
|
||||||
|
import {
|
||||||
|
asyncUpdates,
|
||||||
|
syncUpdates,
|
||||||
|
runDiscreteUpdates,
|
||||||
|
launchUpdateFromVNode,
|
||||||
|
} from './TreeBuilder';
|
||||||
|
import {runAsyncEffects} from './submit/HookEffectHandler';
|
||||||
|
import {Callback, newUpdate, pushUpdate} from './UpdateHandler';
|
||||||
|
import {getFirstChild} from './vnode/VNodeUtils';
|
||||||
|
|
||||||
|
export {createVNode} from './vnode/VNodeCreator';
|
||||||
|
export {createPortal} from './components/CreatePortal';
|
||||||
|
export {
|
||||||
|
asyncUpdates,
|
||||||
|
syncUpdates,
|
||||||
|
runDiscreteUpdates,
|
||||||
|
runAsyncEffects,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function startUpdate(
|
||||||
|
element: any,
|
||||||
|
treeRoot: VNode,
|
||||||
|
callback?: Callback,
|
||||||
|
) {
|
||||||
|
const update: Update = newUpdate();
|
||||||
|
update.content = {element};
|
||||||
|
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
update.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
pushUpdate(treeRoot, update);
|
||||||
|
|
||||||
|
launchUpdateFromVNode(treeRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getFirstCustomDom(treeRoot: VNode): Element | Text | null {
|
||||||
|
const firstChild = getFirstChild(treeRoot);
|
||||||
|
if (!firstChild) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return firstChild.realNode;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
export { VNode } from './vnode/VNode';
|
||||||
|
|
||||||
|
type Trigger<A> = (A) => void;
|
||||||
|
|
||||||
|
export type ReadContextHookType = { readContext<T>(context: ContextType<T>): T };
|
||||||
|
export type UseStateHookType = {
|
||||||
|
useState<S>(
|
||||||
|
initialState: (() => S) | S
|
||||||
|
): [S, Trigger<((S) => S) | S>]
|
||||||
|
};
|
||||||
|
export type UseReducerHookType = {
|
||||||
|
useReducer<S, P, A>(
|
||||||
|
reducer: (S, A) => S,
|
||||||
|
initArg: P, init?: (P) => S,
|
||||||
|
): [S, Trigger<A>]
|
||||||
|
};
|
||||||
|
export type UseContextHookType = { useContext<T>(context: ContextType<T>,): T };
|
||||||
|
|
||||||
|
export type HorizonElement = {
|
||||||
|
vtype: any,
|
||||||
|
type: any,
|
||||||
|
key: any,
|
||||||
|
ref: any,
|
||||||
|
props: any,
|
||||||
|
|
||||||
|
_vNode: any,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ProviderType<T> = {
|
||||||
|
vtype: number;
|
||||||
|
_context: ContextType<T>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ContextType<T> = {
|
||||||
|
vtype: number;
|
||||||
|
Consumer: ContextType<T>;
|
||||||
|
Provider: ProviderType<T>;
|
||||||
|
value: T;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PortalType = {
|
||||||
|
vtype: number;
|
||||||
|
key: null | string;
|
||||||
|
outerDom: any;
|
||||||
|
children: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RefType = {
|
||||||
|
current: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface PromiseType<R> {
|
||||||
|
then<U>(
|
||||||
|
onFulfill: (value: R) => void | PromiseType<U> | U,
|
||||||
|
onReject: (error: any) => void | PromiseType<U> | U,
|
||||||
|
): void | PromiseType<U>;
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
/**
|
/**
|
||||||
* Component的api setState和forceUpdate在实例生成阶段实现
|
* Component的api setState和forceUpdate在实例生成阶段实现
|
||||||
*/
|
*/
|
||||||
class Component {
|
class Component<P,S,C> {
|
||||||
props;
|
props: P;
|
||||||
context;
|
context: C;
|
||||||
|
state: S;
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props: P, context: C) {
|
||||||
this.props = props;
|
this.props = props;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
@ -16,8 +17,8 @@ Component.prototype.isReactComponent = true;
|
||||||
/**
|
/**
|
||||||
* 支持PureComponent
|
* 支持PureComponent
|
||||||
*/
|
*/
|
||||||
class PureComponent extends Component {
|
class PureComponent<P, S, C> extends Component<P, S, C> {
|
||||||
constructor(props, context) {
|
constructor(props: P, context: C) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ type LazyContent<T> = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LazyComponent<T, P> = {
|
export type LazyComponent<T, P> = {
|
||||||
vtype: Symbol | number,
|
vtype: number,
|
||||||
_content: P,
|
_content: P,
|
||||||
_load: (content: P) => T,
|
_load: (content: P) => T,
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,7 +3,8 @@ import type {Hook} from './HookType';
|
||||||
|
|
||||||
let processingVNode: VNode = null;
|
let processingVNode: VNode = null;
|
||||||
|
|
||||||
let activatedHook: Hook<any, any> | null = null;
|
// lastTimeHook是上一次执行func时产生的hooks中,与currentHook对应的hook
|
||||||
|
let lastTimeHook: Hook<any, any> | null = null;
|
||||||
|
|
||||||
// 当前hook函数对应的hook对象
|
// 当前hook函数对应的hook对象
|
||||||
let currentHook: Hook<any, any> | null = null;
|
let currentHook: Hook<any, any> | null = null;
|
||||||
|
@ -16,12 +17,12 @@ export function setProcessingVNode(vNode: VNode) {
|
||||||
processingVNode = vNode;
|
processingVNode = vNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getActivatedHook() {
|
export function getLastTimeHook() {
|
||||||
return activatedHook;
|
return lastTimeHook;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setActivatedHook(hook: Hook<any, any>) {
|
export function setLastTimeHook(hook: Hook<any, any>) {
|
||||||
activatedHook = hook;
|
lastTimeHook = hook;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setCurrentHook(hook: Hook<any, any>) {
|
export function setCurrentHook(hook: Hook<any, any>) {
|
||||||
|
@ -47,24 +48,32 @@ export function createHook(state: any = null): Hook<any, any> {
|
||||||
return currentHook;
|
return currentHook;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNextHook(hook: Hook<any, any>, vNode: VNode) {
|
export function getNextHook(hook: Hook<any, any>, hooks: Array<Hook<any, any>>) {
|
||||||
return vNode.hooks[hook.hIndex + 1] || null;
|
return hooks[hook.hIndex + 1] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取当前hook函数对应的hook对象。
|
// 获取当前hook函数对应的hook对象。
|
||||||
// processing中的hook和activated中的hook,需要同时往前走,
|
// processing中的hook和上一次执行中的hook,需要同时往前走,
|
||||||
// 原因:1.比对hook的数量有没有变化(非必要);2.从activated中的hook获取removeEffect
|
// 原因:1.比对hook的数量有没有变化(非必要);2.从上一次执行中的hook获取removeEffect
|
||||||
export function getCurrentHook(): Hook<any, any> {
|
export function getCurrentHook(): Hook<any, any> {
|
||||||
currentHook = currentHook !== null ? getNextHook(currentHook, processingVNode) : (processingVNode.hooks[0] || null);
|
currentHook = currentHook !== null ? getNextHook(currentHook, processingVNode.hooks) : (processingVNode.hooks[0] || null);
|
||||||
const activated = processingVNode.twins;
|
|
||||||
activatedHook = activatedHook !== null ? getNextHook(activatedHook, activated) : ((activated && activated.hooks[0]) || null);
|
if (lastTimeHook !== null) {
|
||||||
|
lastTimeHook = getNextHook(lastTimeHook, processingVNode.oldHooks);
|
||||||
|
} else {
|
||||||
|
if (processingVNode.oldHooks && processingVNode.oldHooks.length) {
|
||||||
|
lastTimeHook = processingVNode.oldHooks[0];
|
||||||
|
} else {
|
||||||
|
lastTimeHook = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (currentHook === null) {
|
if (currentHook === null) {
|
||||||
if (activatedHook === null) {
|
if (lastTimeHook === null) {
|
||||||
throw Error('Hooks are more than expected, please check whether the hook is written in the condition.');
|
throw Error('Hooks are more than expected, please check whether the hook is written in the condition.');
|
||||||
}
|
}
|
||||||
|
|
||||||
createHook(activatedHook.state);
|
createHook(lastTimeHook.state);
|
||||||
}
|
}
|
||||||
|
|
||||||
return currentHook;
|
return currentHook;
|
||||||
|
|
|
@ -9,9 +9,9 @@ const {
|
||||||
|
|
||||||
import {getNewContext} from '../components/context/Context';
|
import {getNewContext} from '../components/context/Context';
|
||||||
import {
|
import {
|
||||||
getActivatedHook,
|
getLastTimeHook,
|
||||||
getProcessingVNode,
|
getProcessingVNode,
|
||||||
setActivatedHook,
|
setLastTimeHook,
|
||||||
setProcessingVNode,
|
setProcessingVNode,
|
||||||
setCurrentHook, getNextHook
|
setCurrentHook, getNextHook
|
||||||
} from './BaseHook';
|
} from './BaseHook';
|
||||||
|
@ -24,7 +24,6 @@ export function exeFunctionHook(
|
||||||
funcComp: (props: Object, arg: Object) => any,
|
funcComp: (props: Object, arg: Object) => any,
|
||||||
props: Object,
|
props: Object,
|
||||||
arg: Object,
|
arg: Object,
|
||||||
activated: VNode | null,
|
|
||||||
processing: VNode,
|
processing: VNode,
|
||||||
): any {
|
): any {
|
||||||
// 重置全局变量
|
// 重置全局变量
|
||||||
|
@ -35,11 +34,12 @@ export function exeFunctionHook(
|
||||||
|
|
||||||
setProcessingVNode(processing);
|
setProcessingVNode(processing);
|
||||||
|
|
||||||
|
processing.oldHooks = processing.hooks;
|
||||||
processing.hooks = [];
|
processing.hooks = [];
|
||||||
processing.effectList = [];
|
processing.effectList = [];
|
||||||
|
|
||||||
// 设置hook阶段
|
// 设置hook阶段
|
||||||
if (activated === null || !activated.hooks.length) {
|
if (processing.isCreated || !processing.oldHooks.length) {
|
||||||
setHookStage(HookStage.Init);
|
setHookStage(HookStage.Init);
|
||||||
} else {
|
} else {
|
||||||
setHookStage(HookStage.Update);
|
setHookStage(HookStage.Update);
|
||||||
|
@ -51,9 +51,9 @@ export function exeFunctionHook(
|
||||||
setHookStage(null);
|
setHookStage(null);
|
||||||
|
|
||||||
// 判断hook是否写在了if条件中,如果在if中会出现数量不对等的情况
|
// 判断hook是否写在了if条件中,如果在if中会出现数量不对等的情况
|
||||||
const activatedHook = getActivatedHook();
|
const lastTimeHook = getLastTimeHook();
|
||||||
if (activatedHook !== null) {
|
if (lastTimeHook !== null) {
|
||||||
if (getNextHook(getActivatedHook(), activated) !== null) {
|
if (getNextHook(getLastTimeHook(), processing.oldHooks) !== null) {
|
||||||
throw Error('Hooks are less than expected, please check whether the hook is written in the condition.');
|
throw Error('Hooks are less than expected, please check whether the hook is written in the condition.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ export function exeFunctionHook(
|
||||||
function resetGlobalVariable() {
|
function resetGlobalVariable() {
|
||||||
setHookStage(null);
|
setHookStage(null);
|
||||||
setProcessingVNode(null);
|
setProcessingVNode(null);
|
||||||
setActivatedHook(null);
|
setLastTimeHook(null);
|
||||||
setCurrentHook(null);
|
setCurrentHook(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import {EffectConstant} from './EffectConstant';
|
import {EffectConstant} from './EffectConstant';
|
||||||
import {VNode} from '../Types';
|
|
||||||
|
|
||||||
export interface Hook<S, A> {
|
export interface Hook<S, A> {
|
||||||
state: Reducer<S, A> | Effect | Memo<S> | CallBack<S> | Ref<S>;
|
state: Reducer<S, A> | Effect | Memo<S> | CallBack<S> | Ref<S>;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {
|
import {
|
||||||
createHook,
|
createHook,
|
||||||
getCurrentHook,
|
getCurrentHook,
|
||||||
getActivatedHook,
|
getLastTimeHook,
|
||||||
getProcessingVNode, throwNotInFuncError
|
getProcessingVNode, throwNotInFuncError
|
||||||
} from './BaseHook';
|
} from './BaseHook';
|
||||||
import {FlagUtils} from '../vnode/VNodeFlags';
|
import {FlagUtils} from '../vnode/VNodeFlags';
|
||||||
|
@ -38,10 +38,10 @@ function useEffect(
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEffectForInit(effectFunc, deps, effectType): void {
|
export function useEffectForInit(effectFunc, deps, effectType): void {
|
||||||
const hook = createHook();
|
|
||||||
const nextDeps = deps !== undefined ? deps : null;
|
|
||||||
FlagUtils.markUpdate(getProcessingVNode());
|
FlagUtils.markUpdate(getProcessingVNode());
|
||||||
|
|
||||||
|
const hook = createHook();
|
||||||
|
const nextDeps = deps !== undefined ? deps : null;
|
||||||
// 初始阶段,设置DepsChange标记位; 构造EffectList数组,并赋值给state
|
// 初始阶段,设置DepsChange标记位; 构造EffectList数组,并赋值给state
|
||||||
hook.state = createEffect(effectFunc, undefined, nextDeps, EffectConstant.DepsChange | effectType);
|
hook.state = createEffect(effectFunc, undefined, nextDeps, EffectConstant.DepsChange | effectType);
|
||||||
}
|
}
|
||||||
|
@ -51,13 +51,13 @@ export function useEffectForUpdate(effectFunc, deps, effectType): void {
|
||||||
const nextDeps = deps !== undefined ? deps : null;
|
const nextDeps = deps !== undefined ? deps : null;
|
||||||
let removeFunc;
|
let removeFunc;
|
||||||
|
|
||||||
if (getActivatedHook() !== null) {
|
if (getLastTimeHook() !== null) {
|
||||||
const effect = getActivatedHook().state as Effect;
|
const effect = getLastTimeHook().state as Effect;
|
||||||
// removeEffect是通过执行effect返回的,所以需要在activated中获取
|
// removeEffect是通过执行effect返回的,所以需要在上一次hook中获取
|
||||||
removeFunc = effect.removeEffect;
|
removeFunc = effect.removeEffect;
|
||||||
const lastDeps = effect.dependencies;
|
const lastDeps = effect.dependencies;
|
||||||
|
|
||||||
// 判断dependencies是否相同,不同就不设置DepsChange标记位
|
// 判断dependencies是否相同,同相同不需要设置DepsChange标记位
|
||||||
if (nextDeps !== null && isArrayEqual(nextDeps, lastDeps)) {
|
if (nextDeps !== null && isArrayEqual(nextDeps, lastDeps)) {
|
||||||
hook.state = createEffect(effectFunc, removeFunc, nextDeps, effectType);
|
hook.state = createEffect(effectFunc, removeFunc, nextDeps, effectType);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -54,10 +54,9 @@ function insertUpdate<S, A>(action: A, hook: Hook<S, A>): Update<S, A> {
|
||||||
// setState, setReducer触发函数
|
// setState, setReducer触发函数
|
||||||
export function TriggerAction<S, A, T>(vNode: VNode, hook: Hook<S, A>, action: A) {
|
export function TriggerAction<S, A, T>(vNode: VNode, hook: Hook<S, A>, action: A) {
|
||||||
const newUpdate = insertUpdate(action, hook);
|
const newUpdate = insertUpdate(action, hook);
|
||||||
const twins = vNode.twins;
|
|
||||||
|
|
||||||
// 判断是否需要刷新
|
// 判断是否需要刷新
|
||||||
if (!vNode.shouldUpdate && (twins === null || !twins.shouldUpdate)) {
|
if (!vNode.shouldUpdate) {
|
||||||
const reducerObj = hook.state as Reducer<S, A>;
|
const reducerObj = hook.state as Reducer<S, A>;
|
||||||
const { stateValue, reducer } = reducerObj;
|
const { stateValue, reducer } = reducerObj;
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ function callTasks(initialTime) {
|
||||||
task.expirationTime > currentTime &&
|
task.expirationTime > currentTime &&
|
||||||
isOverTime()
|
isOverTime()
|
||||||
) {
|
) {
|
||||||
// 没到deadline
|
// 任务的过期时间大于当前时间(没达到此任务的过期时间)且超过了deadline
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
/**
|
||||||
|
* 兼容IE浏览器没有Object.is
|
||||||
|
*/
|
||||||
|
export function isSame(x: any, y: any) {
|
||||||
|
if (!(typeof Object.is === 'function')) {
|
||||||
|
if (x === y) {
|
||||||
|
// +0 != -0
|
||||||
|
return x !== 0 || 1 / x === 1 / y;
|
||||||
|
} else {
|
||||||
|
// NaN == NaN
|
||||||
|
return x !== x && y !== y;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Object.is(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isArrayEqual(nextParam: Array<any>, lastParam: Array<any> | null) {
|
||||||
|
if (lastParam === null || lastParam.length !== nextParam.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < lastParam.length; i++) {
|
||||||
|
if (!isSame(nextParam[i], lastParam[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shallowCompare(paramX: any, paramY: any): boolean {
|
||||||
|
if (isSame(paramX, paramY)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对比对象
|
||||||
|
if (typeof paramX === 'object' && typeof paramY === 'object' && paramX !== null && paramY !== null) {
|
||||||
|
const keysX = Object.keys(paramX);
|
||||||
|
const keysY = Object.keys(paramY);
|
||||||
|
|
||||||
|
// key长度不相等时直接返回不相等
|
||||||
|
if (keysX.length !== keysY.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return keysX.every((key, i) => {
|
||||||
|
return Object.prototype.hasOwnProperty.call(paramY, key) && isSame(paramX[key], paramY[keysX[i]]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
export const TYPE_ELEMENT = 1;
|
||||||
|
export const TYPE_PORTAL = 2;
|
||||||
|
export const TYPE_FRAGMENT = 3;
|
||||||
|
export const TYPE_STRICT_MODE = 4;
|
||||||
|
export const TYPE_PROVIDER = 5;
|
||||||
|
export const TYPE_CONTEXT = 6;
|
||||||
|
export const TYPE_FORWARD_REF = 7;
|
||||||
|
export const TYPE_SUSPENSE = 8;
|
||||||
|
export const TYPE_PROFILER = 9;
|
||||||
|
export const TYPE_MEMO = 10;
|
||||||
|
export const TYPE_LAZY = 11;
|
|
@ -0,0 +1,13 @@
|
||||||
|
// 当条件不成立报错
|
||||||
|
// 接收模板
|
||||||
|
export function throwIfTrue(condition: boolean, errTemplate: string, ...errExpressions: string[]) {
|
||||||
|
if (condition) {
|
||||||
|
// 将%s 替换成对应的变量
|
||||||
|
const msg = errTemplate.split('%s').reduce((prevSentence: string, part: string, idx: number) => {
|
||||||
|
// %s对应的变量
|
||||||
|
const expression = idx < errExpressions.length ? errExpressions[idx] : '' ;
|
||||||
|
return prevSentence + part + expression;
|
||||||
|
}, '');
|
||||||
|
throw Error(msg);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,191 +0,0 @@
|
||||||
import type { VNodeTag } from './VNodeTags';
|
|
||||||
import { FlagUtils } from './VNodeFlags';
|
|
||||||
import {
|
|
||||||
ClassComponent,
|
|
||||||
ContextConsumer,
|
|
||||||
ContextProvider,
|
|
||||||
ForwardRef,
|
|
||||||
Fragment,
|
|
||||||
FunctionComponent,
|
|
||||||
DomComponent,
|
|
||||||
DomPortal,
|
|
||||||
TreeRoot,
|
|
||||||
DomText,
|
|
||||||
ClsOrFunComponent,
|
|
||||||
LazyComponent,
|
|
||||||
MemoComponent,
|
|
||||||
SuspenseComponent,
|
|
||||||
} from './VNodeTags';
|
|
||||||
import { createUpdateArray } from '../UpdateHandler';
|
|
||||||
import {
|
|
||||||
TYPE_CONTEXT,
|
|
||||||
TYPE_FORWARD_REF, TYPE_FRAGMENT,
|
|
||||||
TYPE_LAZY,
|
|
||||||
TYPE_MEMO, TYPE_PROFILER,
|
|
||||||
TYPE_PROVIDER, TYPE_STRICT_MODE,
|
|
||||||
TYPE_SUSPENSE,
|
|
||||||
} from '../utils/elementType';
|
|
||||||
import { VNode } from './VNode';
|
|
||||||
import {HorizonElement} from '../Types';
|
|
||||||
|
|
||||||
const typeLazyMap = {
|
|
||||||
[TYPE_FORWARD_REF]: ForwardRef,
|
|
||||||
[TYPE_MEMO]: MemoComponent,
|
|
||||||
};
|
|
||||||
const typeMap = {
|
|
||||||
...typeLazyMap,
|
|
||||||
[TYPE_PROVIDER]: ContextProvider,
|
|
||||||
[TYPE_CONTEXT]: ContextConsumer,
|
|
||||||
[TYPE_LAZY]: LazyComponent,
|
|
||||||
};
|
|
||||||
|
|
||||||
const newVirtualNode = function(tag: VNodeTag, key?: null | string, vNodeProps?: any, outerDom?: any): VNode {
|
|
||||||
return new VNode(tag, vNodeProps, key, outerDom);
|
|
||||||
};
|
|
||||||
|
|
||||||
function isClassComponent(comp: Function) {
|
|
||||||
// 如果使用 getPrototypeOf 方法获取构造函数,不能兼容业务组组件继承组件的使用方式,会误认为是函数组件
|
|
||||||
// 如果使用静态属性,部分函数高阶组件会将类组件的静态属性复制到自身,导致误判为类组件
|
|
||||||
// 既然已经兼容使用了该标识符,那么继续使用
|
|
||||||
return comp.prototype?.isReactComponent === true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析懒组件的tag
|
|
||||||
export function getLazyVNodeTag(lazyComp: any): string {
|
|
||||||
let vNodeTag = ClsOrFunComponent;
|
|
||||||
if (typeof lazyComp === 'function') {
|
|
||||||
vNodeTag = isClassComponent(lazyComp) ? ClassComponent : FunctionComponent;
|
|
||||||
} else if (lazyComp !== undefined && lazyComp !== null && typeLazyMap[lazyComp.vtype]) {
|
|
||||||
vNodeTag = typeLazyMap[lazyComp.vtype];
|
|
||||||
}
|
|
||||||
return vNodeTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建processing
|
|
||||||
export function updateVNode(vNode: VNode, vNodeProps?: any): VNode {
|
|
||||||
if (vNode.tag === ClassComponent) {
|
|
||||||
vNode.oldState = vNode.state;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (vNode.tag === SuspenseComponent) {
|
|
||||||
vNode.oldSuspenseChildStatus = vNode.suspenseChildStatus;
|
|
||||||
vNode.oldChildren = vNode.children;
|
|
||||||
}
|
|
||||||
|
|
||||||
vNode.oldProps = vNode.props;
|
|
||||||
vNode.props = vNodeProps;
|
|
||||||
|
|
||||||
vNode.oldRef = vNode.ref;
|
|
||||||
|
|
||||||
FlagUtils.setNoFlags(vNode);
|
|
||||||
vNode.dirtyNodes = [];
|
|
||||||
vNode.isCreated = false;
|
|
||||||
|
|
||||||
return vNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVNodeTag(type: any) {
|
|
||||||
let vNodeTag = ClsOrFunComponent;
|
|
||||||
let isLazy = false;
|
|
||||||
|
|
||||||
if (typeof type === 'function') {
|
|
||||||
if (isClassComponent(type)) {
|
|
||||||
vNodeTag = ClassComponent;
|
|
||||||
}
|
|
||||||
} else if (typeof type === 'string') {
|
|
||||||
vNodeTag = DomComponent;
|
|
||||||
} else if (type === TYPE_SUSPENSE) {
|
|
||||||
vNodeTag = SuspenseComponent;
|
|
||||||
} else if (typeof type === 'object' && type !== null && typeMap[type.vtype]) {
|
|
||||||
vNodeTag = typeMap[type.vtype];
|
|
||||||
isLazy = type.vtype === TYPE_LAZY;
|
|
||||||
} else {
|
|
||||||
throw Error(`Component type is invalid, got: ${type == null ? type : typeof type}`);
|
|
||||||
}
|
|
||||||
return { vNodeTag, isLazy };
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createVNode(tag: VNodeTag | string, ...secondArg) {
|
|
||||||
let vNode = null;
|
|
||||||
switch (tag) {
|
|
||||||
case Fragment:
|
|
||||||
const [fragmentKey, fragmentProps] = secondArg;
|
|
||||||
vNode = newVirtualNode(Fragment, fragmentKey, fragmentProps);
|
|
||||||
vNode.shouldUpdate = true;
|
|
||||||
break;
|
|
||||||
case DomText:
|
|
||||||
const content = secondArg[0];
|
|
||||||
vNode = newVirtualNode(DomText, null, content);
|
|
||||||
vNode.shouldUpdate = true;
|
|
||||||
break;
|
|
||||||
case DomPortal:
|
|
||||||
const portal = secondArg[0];
|
|
||||||
const children = portal.children ?? [];
|
|
||||||
vNode = newVirtualNode(DomPortal, portal.key, children);
|
|
||||||
vNode.shouldUpdate = true;
|
|
||||||
vNode.outerDom = portal.outerDom;
|
|
||||||
break;
|
|
||||||
case 'props':
|
|
||||||
const [type, key, props] = secondArg;
|
|
||||||
|
|
||||||
const { vNodeTag, isLazy } = getVNodeTag(type);
|
|
||||||
|
|
||||||
vNode = newVirtualNode(vNodeTag, key, props);
|
|
||||||
vNode.type = type;
|
|
||||||
vNode.shouldUpdate = true;
|
|
||||||
|
|
||||||
// lazy类型的特殊处理
|
|
||||||
vNode.isLazyComponent = isLazy;
|
|
||||||
if (isLazy) {
|
|
||||||
vNode.lazyType = type;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case TreeRoot:
|
|
||||||
// 创建treeRoot
|
|
||||||
vNode = newVirtualNode(TreeRoot, null, null, secondArg[0]);
|
|
||||||
vNode.path.push(0);
|
|
||||||
|
|
||||||
createUpdateArray(vNode);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return vNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateVNodePath(vNode: VNode) {
|
|
||||||
vNode.path = [...vNode.parent.path, vNode.cIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createVNodeFromElement(element: HorizonElement): VNode {
|
|
||||||
const type = element.type;
|
|
||||||
const key = element.key;
|
|
||||||
const props = element.props;
|
|
||||||
|
|
||||||
if (type === TYPE_STRICT_MODE || type === TYPE_FRAGMENT || type === TYPE_PROFILER) {
|
|
||||||
return createVNode(Fragment, key, props.children);
|
|
||||||
} else {
|
|
||||||
return createVNode('props', type, key, props);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 直接更新子节点属性即可,不需要diff
|
|
||||||
export function onlyUpdateChildVNodes(processing: VNode): Array<VNode> | null {
|
|
||||||
// 检查子树是否需要更新
|
|
||||||
if (processing.childShouldUpdate) {
|
|
||||||
// 此vNode无需更新,但是子树需要
|
|
||||||
if (!processing.isCreated && processing.children && processing.children.length) {
|
|
||||||
// 更新子节点
|
|
||||||
processing.children.forEach(child => {
|
|
||||||
updateVNode(child, child.props);
|
|
||||||
updateVNodePath(child);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 返回子节点,继续遍历
|
|
||||||
return processing.children;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 子树无需工作
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue