From 42410259008d352bae65b2bf43dbb088669a138e Mon Sep 17 00:00:00 2001 From: * <*> Date: Tue, 11 Apr 2023 20:05:55 +0800 Subject: [PATCH 1/7] Match-id-ff40d6262153f5fe9dd2054ce2d145237a7545a0 --- libs/horizon/src/event/FormValueController.ts | 58 ++++----- libs/horizon/src/event/HorizonEventMain.ts | 2 +- libs/horizon/src/horizonx/adapters/redux.ts | 24 ++-- libs/horizon/src/renderer/ErrorHandler.ts | 2 +- libs/horizon/src/renderer/hooks/HookMain.ts | 12 +- .../src/renderer/hooks/UseEffectHook.ts | 58 ++++----- .../src/renderer/hooks/UseImperativeHook.ts | 28 ++--- .../src/renderer/hooks/UseReducerHook.ts | 76 ++++++------ .../src/renderer/render/BaseComponent.ts | 2 +- .../src/renderer/render/SuspenseComponent.ts | 2 +- .../src/renderer/submit/HookEffectHandler.ts | 44 +++---- .../src/renderer/submit/LifeCycleHandler.ts | 30 +++-- libs/horizon/src/renderer/submit/Submit.ts | 114 +++++++++--------- .../src/renderer/taskExecutor/BrowserAsync.ts | 16 +-- .../src/renderer/taskExecutor/RenderQueue.ts | 20 +-- libs/horizon/src/renderer/vnode/VNode.ts | 2 + .../src/renderer/vnode/VNodeCreator.ts | 2 + libs/horizon/src/renderer/vnode/VNodeUtils.ts | 14 +-- 18 files changed, 253 insertions(+), 253 deletions(-) diff --git a/libs/horizon/src/event/FormValueController.ts b/libs/horizon/src/event/FormValueController.ts index ac0603e8..3c63a299 100644 --- a/libs/horizon/src/event/FormValueController.ts +++ b/libs/horizon/src/event/FormValueController.ts @@ -37,35 +37,6 @@ export function shouldControlValue(): boolean { return changeEventTargets !== null && changeEventTargets.length > 0; } -// 从缓存队列中对受控组件进行赋值 -export function tryControlValue() { - if (!changeEventTargets) { - return; - } - changeEventTargets.forEach(target => { - controlValue(target); - }); - changeEventTargets = null; -} - -// 受控组件值重新赋值 -function controlValue(target: Element) { - const props = getVNodeProps(target); - if (props) { - const type = getDomTag(target); - switch (type) { - case 'input': - controlInputValue(target, props); - break; - case 'textarea': - updateTextareaValue(target, props); - break; - default: - break; - } - } -} - function controlInputValue(inputDom: HTMLInputElement, props: Props) { const { name, type } = props; @@ -87,3 +58,32 @@ function controlInputValue(inputDom: HTMLInputElement, props: Props) { updateInputValue(inputDom, props); } } + +// 受控组件值重新赋值 +function controlValue(target: Element) { + const props = getVNodeProps(target); + if (props) { + const type = getDomTag(target); + switch (type) { + case 'input': + controlInputValue(target, props); + break; + case 'textarea': + updateTextareaValue(target, props); + break; + default: + break; + } + } +} + +// 从缓存队列中对受控组件进行赋值 +export function tryControlValue() { + if (!changeEventTargets) { + return; + } + changeEventTargets.forEach(target => { + controlValue(target); + }); + changeEventTargets = null; +} diff --git a/libs/horizon/src/event/HorizonEventMain.ts b/libs/horizon/src/event/HorizonEventMain.ts index 5c0ae747..77f22988 100644 --- a/libs/horizon/src/event/HorizonEventMain.ts +++ b/libs/horizon/src/event/HorizonEventMain.ts @@ -45,7 +45,7 @@ function shouldTriggerChangeEvent(targetDom, evtName) { const { type } = targetDom; const domTag = getDomTag(targetDom); - if (domTag === 'select' || (domTag === 'input' && type === 'file')) { + if (domTag === 'select' || domTag === 'input' && type === 'file') { return evtName === 'change'; } else if (domTag === 'input' && (type === 'checkbox' || type === 'radio')) { if (evtName === 'click') { diff --git a/libs/horizon/src/horizonx/adapters/redux.ts b/libs/horizon/src/horizonx/adapters/redux.ts index 03ad253b..46d4b663 100644 --- a/libs/horizon/src/horizonx/adapters/redux.ts +++ b/libs/horizon/src/horizonx/adapters/redux.ts @@ -54,15 +54,12 @@ export type ReduxMiddleware = ( type Reducer = (state: any, action: ReduxAction) => any; function mergeData(state, data) { - console.log('merging data', { state, data }); if (!data) { - console.log('!data'); state.stateWrapper = data; return; } if (Array.isArray(data) && Array.isArray(state?.stateWrapper)) { - console.log('data is array'); state.stateWrapper.length = data.length; data.forEach((item, idx) => { if (item != state.stateWrapper[idx]) { @@ -73,9 +70,8 @@ function mergeData(state, data) { } if (typeof data === 'object' && typeof state?.stateWrapper === 'object') { - console.log('data is object'); Object.keys(state.stateWrapper).forEach(key => { - if (!data.hasOwnProperty(key)) delete state.stateWrapper[key]; + if (!Object.prototype.hasOwnProperty.call(data, key)) delete state.stateWrapper[key]; }); Object.entries(data).forEach(([key, value]) => { @@ -86,7 +82,6 @@ function mergeData(state, data) { return; } - console.log('data is primitive or type mismatch'); state.stateWrapper = data; } @@ -106,7 +101,6 @@ export function createStore(reducer: Reducer, preloadedState?: any, enhancers?): if (result === undefined) { return; } // NOTE: reducer should never return undefined, in this case, do not change state - // mergeData(state,result); state.stateWrapper = result; }, }, @@ -115,10 +109,6 @@ export function createStore(reducer: Reducer, preloadedState?: any, enhancers?): }, })(); - // store.$subscribe(()=>{ - // console.log('changed'); - // }); - const result = { reducer, getState: function () { @@ -157,12 +147,6 @@ export function combineReducers(reducers: { [key: string]: Reducer }): Reducer { }; } -export function applyMiddleware(...middlewares: ReduxMiddleware[]): (store: ReduxStoreHandler) => void { - return store => { - return applyMiddlewares(store, middlewares); - }; -} - function applyMiddlewares(store: ReduxStoreHandler, middlewares: ReduxMiddleware[]): void { middlewares = middlewares.slice(); middlewares.reverse(); @@ -173,6 +157,12 @@ function applyMiddlewares(store: ReduxStoreHandler, middlewares: ReduxMiddleware store.dispatch = dispatch; } +export function applyMiddleware(...middlewares: ReduxMiddleware[]): (store: ReduxStoreHandler) => void { + return store => { + return applyMiddlewares(store, middlewares); + }; +} + type ActionCreator = (...params: any[]) => ReduxAction; type ActionCreators = { [key: string]: ActionCreator }; export type BoundActionCreator = (...params: any[]) => void; diff --git a/libs/horizon/src/renderer/ErrorHandler.ts b/libs/horizon/src/renderer/ErrorHandler.ts index 7ba23cec..efcba91d 100644 --- a/libs/horizon/src/renderer/ErrorHandler.ts +++ b/libs/horizon/src/renderer/ErrorHandler.ts @@ -110,7 +110,7 @@ export function handleRenderThrowError(sourceVNode: VNode, error: any) { if ( (vNode.flags & DidCapture) === InitFlag && (typeof ctor.getDerivedStateFromError === 'function' || - (instance !== null && typeof instance.componentDidCatch === 'function')) + instance !== null && typeof instance.componentDidCatch === 'function') ) { FlagUtils.markShouldCapture(vNode); diff --git a/libs/horizon/src/renderer/hooks/HookMain.ts b/libs/horizon/src/renderer/hooks/HookMain.ts index 31a5be53..f22376f3 100644 --- a/libs/horizon/src/renderer/hooks/HookMain.ts +++ b/libs/horizon/src/renderer/hooks/HookMain.ts @@ -18,6 +18,12 @@ import type { VNode } from '../Types'; import { getLastTimeHook, setLastTimeHook, setCurrentHook, getNextHook } from './BaseHook'; import { HookStage, setHookStage } from './HookStage'; +function resetGlobalVariable() { + setHookStage(null); + setLastTimeHook(null); + setCurrentHook(null); +} + // hook对外入口 export function runFunctionWithHooks, Arg>( funcComp: (props: Props, arg: Arg) => any, @@ -57,9 +63,3 @@ export function runFunctionWithHooks, Arg>( return comp; } - -function resetGlobalVariable() { - setHookStage(null); - setLastTimeHook(null); - setCurrentHook(null); -} diff --git a/libs/horizon/src/renderer/hooks/UseEffectHook.ts b/libs/horizon/src/renderer/hooks/UseEffectHook.ts index cd8e4313..9d11c01d 100644 --- a/libs/horizon/src/renderer/hooks/UseEffectHook.ts +++ b/libs/horizon/src/renderer/hooks/UseEffectHook.ts @@ -21,27 +21,17 @@ import { getHookStage, HookStage } from './HookStage'; import { isArrayEqual } from '../utils/compare'; import { getProcessingVNode } from '../GlobalVar'; -export function useEffectImpl(effectFunc: () => (() => void) | void, deps?: Array | null): void { - // 异步触发的effect - useEffect(effectFunc, deps, EffectConstant.Effect); -} +function createEffect(effectFunc, removeFunc, deps, effectConstant): Effect { + const effect: Effect = { + effect: effectFunc, + removeEffect: removeFunc, + dependencies: deps, + effectConstant: effectConstant, + }; -export function useLayoutEffectImpl(effectFunc: () => (() => void) | void, deps?: Array | null): void { - // 同步触发的effect - useEffect(effectFunc, deps, EffectConstant.LayoutEffect); -} + getProcessingVNode().effectList.push(effect); -function useEffect(effectFunc: () => (() => void) | void, deps: Array | void | null, effectType: number): void { - const stage = getHookStage(); - if (stage === null) { - throwNotInFuncError(); - } - - if (stage === HookStage.Init) { - useEffectForInit(effectFunc, deps, effectType); - } else if (stage === HookStage.Update) { - useEffectForUpdate(effectFunc, deps, effectType); - } + return effect; } export function useEffectForInit(effectFunc, deps, effectType): void { @@ -76,15 +66,25 @@ export function useEffectForUpdate(effectFunc, deps, effectType): void { hook.state = createEffect(effectFunc, removeFunc, nextDeps, EffectConstant.DepsChange | effectType); } -function createEffect(effectFunc, removeFunc, deps, effectConstant): Effect { - const effect: Effect = { - effect: effectFunc, - removeEffect: removeFunc, - dependencies: deps, - effectConstant: effectConstant, - }; +function useEffect(effectFunc: () => (() => void) | void, deps: Array | void | null, effectType: number): void { + const stage = getHookStage(); + if (stage === null) { + throwNotInFuncError(); + } - getProcessingVNode().effectList.push(effect); - - return effect; + if (stage === HookStage.Init) { + useEffectForInit(effectFunc, deps, effectType); + } else if (stage === HookStage.Update) { + useEffectForUpdate(effectFunc, deps, effectType); + } +} + +export function useEffectImpl(effectFunc: () => (() => void) | void, deps?: Array | null): void { + // 异步触发的effect + useEffect(effectFunc, deps, EffectConstant.Effect); +} + +export function useLayoutEffectImpl(effectFunc: () => (() => void) | void, deps?: Array | null): void { + // 同步触发的effect + useEffect(effectFunc, deps, EffectConstant.LayoutEffect); } diff --git a/libs/horizon/src/renderer/hooks/UseImperativeHook.ts b/libs/horizon/src/renderer/hooks/UseImperativeHook.ts index b9f6d581..3ae2feef 100644 --- a/libs/horizon/src/renderer/hooks/UseImperativeHook.ts +++ b/libs/horizon/src/renderer/hooks/UseImperativeHook.ts @@ -18,20 +18,6 @@ import { getHookStage } from './HookStage'; import { throwNotInFuncError } from './BaseHook'; import type { Ref } from './HookType'; -export function useImperativeHandleImpl( - ref: { current: R | null } | ((any) => any) | null | void, - func: () => R, - dependencies?: Array | null -): void { - const stage = getHookStage(); - if (stage === null) { - throwNotInFuncError(); - } - - const params = isNotNull(dependencies) ? dependencies.concat([ref]) : null; - useLayoutEffectImpl(effectFunc.bind(null, func, ref), params); -} - function isNotNull(object: any): boolean { return object !== null && object !== undefined; } @@ -52,3 +38,17 @@ function effectFunc(func: () => R, ref: Ref | ((any) => any) | null): (() }; } } + +export function useImperativeHandleImpl( + ref: { current: R | null } | ((any) => any) | null | void, + func: () => R, + dependencies?: Array | null +): void { + const stage = getHookStage(); + if (stage === null) { + throwNotInFuncError(); + } + + const params = isNotNull(dependencies) ? dependencies.concat([ref]) : null; + useLayoutEffectImpl(effectFunc.bind(null, func, ref), params); +} diff --git a/libs/horizon/src/renderer/hooks/UseReducerHook.ts b/libs/horizon/src/renderer/hooks/UseReducerHook.ts index 3637f775..86572fb7 100644 --- a/libs/horizon/src/renderer/hooks/UseReducerHook.ts +++ b/libs/horizon/src/renderer/hooks/UseReducerHook.ts @@ -22,29 +22,6 @@ import { getHookStage, HookStage } from './HookStage'; import type { VNode } from '../Types'; import { getProcessingVNode } from '../GlobalVar'; -export function useReducerImpl( - reducer: (S, A) => S, - initArg: P, - init?: (P) => S, - isUseState?: boolean -): [S, Trigger] | void { - const stage = getHookStage(); - if (stage === null) { - throwNotInFuncError(); - } - - if (stage === HookStage.Init) { - return useReducerForInit(reducer, initArg, init, isUseState); - } else if (stage === HookStage.Update) { - // 获取当前的hook - const currentHook = getCurrentHook(); - // 获取currentHook的更新数组 - const currentHookUpdates = (currentHook.state as Reducer).updates; - - return updateReducerHookState(currentHookUpdates, currentHook, reducer); - } -} - // 构造新的Update数组 function insertUpdate(action: A, hook: Hook): Update { const newUpdate: Update = { @@ -116,6 +93,25 @@ export function useReducerForInit(reducer, initArg, init, isUseState?: boo return [hook.state.stateValue, trigger]; } +// 计算stateValue值 +function calculateNewState(currentHookUpdates: Array>, currentHook, reducer: (S, A) => S) { + const reducerObj = currentHook.state; + let state = reducerObj.stateValue; + + // 循环遍历更新数组,计算新的状态值 + currentHookUpdates.forEach(update => { + // 1. didCalculated = true 说明state已经计算过; 2. 如果来自 isUseState + if (update.didCalculated && reducerObj.isUseState) { + state = update.state; + } else { + const action = update.action; + state = reducer(state, action); + } + }); + + return state; +} + // 更新hook.state function updateReducerHookState(currentHookUpdates, currentHook, reducer): [S, Trigger] { if (currentHookUpdates !== null) { @@ -135,21 +131,25 @@ function updateReducerHookState(currentHookUpdates, currentHook, reducer): return [currentHook.state.stateValue, currentHook.state.trigger]; } -// 计算stateValue值 -function calculateNewState(currentHookUpdates: Array>, currentHook, reducer: (S, A) => S) { - const reducerObj = currentHook.state; - let state = reducerObj.stateValue; +export function useReducerImpl( + reducer: (S, A) => S, + initArg: P, + init?: (P) => S, + isUseState?: boolean +): [S, Trigger] | void { + const stage = getHookStage(); + if (stage === null) { + throwNotInFuncError(); + } - // 循环遍历更新数组,计算新的状态值 - currentHookUpdates.forEach(update => { - // 1. didCalculated = true 说明state已经计算过; 2. 如果来自 isUseState - if (update.didCalculated && reducerObj.isUseState) { - state = update.state; - } else { - const action = update.action; - state = reducer(state, action); - } - }); + if (stage === HookStage.Init) { + return useReducerForInit(reducer, initArg, init, isUseState); + } else if (stage === HookStage.Update) { + // 获取当前的hook + const currentHook = getCurrentHook(); + // 获取currentHook的更新数组 + const currentHookUpdates = (currentHook.state as Reducer).updates; - return state; + return updateReducerHookState(currentHookUpdates, currentHook, reducer); + } } diff --git a/libs/horizon/src/renderer/render/BaseComponent.ts b/libs/horizon/src/renderer/render/BaseComponent.ts index 27449235..45b5be71 100644 --- a/libs/horizon/src/renderer/render/BaseComponent.ts +++ b/libs/horizon/src/renderer/render/BaseComponent.ts @@ -73,7 +73,7 @@ export function captureVNode(processing: VNode): VNode | null { export function markRef(processing: VNode) { const ref = processing.ref; - if ((processing.isCreated && ref !== null) || (!processing.isCreated && processing.oldRef !== ref)) { + if (processing.isCreated && ref !== null || !processing.isCreated && processing.oldRef !== ref) { FlagUtils.markRef(processing); } } diff --git a/libs/horizon/src/renderer/render/SuspenseComponent.ts b/libs/horizon/src/renderer/render/SuspenseComponent.ts index 82c04dee..a73644c7 100644 --- a/libs/horizon/src/renderer/render/SuspenseComponent.ts +++ b/libs/horizon/src/renderer/render/SuspenseComponent.ts @@ -151,7 +151,7 @@ export function bubbleRender(processing: VNode) { const { childStatus, oldChildStatus } = processing.suspenseState; if ( childStatus === SuspenseChildStatus.ShowFallback || - (!processing.isCreated && oldChildStatus === SuspenseChildStatus.ShowFallback) + !processing.isCreated && oldChildStatus === SuspenseChildStatus.ShowFallback ) { FlagUtils.markUpdate(processing); } diff --git a/libs/horizon/src/renderer/submit/HookEffectHandler.ts b/libs/horizon/src/renderer/submit/HookEffectHandler.ts index c7db9c32..06bfd371 100644 --- a/libs/horizon/src/renderer/submit/HookEffectHandler.ts +++ b/libs/horizon/src/renderer/submit/HookEffectHandler.ts @@ -40,28 +40,6 @@ export function isSchedulingEffects() { return isScheduling; } -export function callUseEffects(vNode: VNode) { - const effectList: EffectList = vNode.effectList; - if (effectList !== null) { - effectList.forEach(effect => { - const { effectConstant } = effect; - if ( - (effectConstant & EffectConstant.Effect) !== EffectConstant.NoEffect && - (effectConstant & EffectConstant.DepsChange) !== EffectConstant.NoEffect - ) { - hookEffects.push(effect); - hookRemoveEffects.push(effect); - - // 异步调用 - if (!isScheduling) { - isScheduling = true; - runAsync(runAsyncEffects); - } - } - }); - } -} - export function runAsyncEffects() { const preMode = copyExecuteMode(); changeMode(InRender, true); @@ -98,6 +76,28 @@ export function runAsyncEffects() { setExecuteMode(preMode); } +export function callUseEffects(vNode: VNode) { + const effectList: EffectList = vNode.effectList; + if (effectList !== null) { + effectList.forEach(effect => { + const { effectConstant } = effect; + if ( + (effectConstant & EffectConstant.Effect) !== EffectConstant.NoEffect && + (effectConstant & EffectConstant.DepsChange) !== EffectConstant.NoEffect + ) { + hookEffects.push(effect); + hookRemoveEffects.push(effect); + + // 异步调用 + if (!isScheduling) { + isScheduling = true; + runAsync(runAsyncEffects); + } + } + }); + } +} + // 在销毁vNode的时候调用remove export function callEffectRemove(vNode: VNode) { const effectList: EffectList = vNode.effectList; diff --git a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts index cd3461b2..ebf73031 100644 --- a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts +++ b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts @@ -154,18 +154,6 @@ function hideOrUnhideAllChildren(vNode, isHidden) { ); } -function attachRef(vNode: VNode) { - const ref = vNode.ref; - - handleRef(vNode, ref, vNode.realNode); -} - -function detachRef(vNode: VNode, isOldRef?: boolean) { - const ref = isOldRef ? vNode.oldRef : vNode.ref; - - handleRef(vNode, ref, null); -} - function handleRef(vNode: VNode, ref, val) { if (ref !== null && ref !== undefined) { const refType = typeof ref; @@ -182,6 +170,18 @@ function handleRef(vNode: VNode, ref, val) { } } +function attachRef(vNode: VNode) { + const ref = vNode.ref; + + handleRef(vNode, ref, vNode.realNode); +} + +function detachRef(vNode: VNode, isOldRef?: boolean) { + const ref = isOldRef ? vNode.oldRef : vNode.ref; + + handleRef(vNode, ref, null); +} + // 卸载一个vNode,不会递归 function unmountVNode(vNode: VNode): void { switch (vNode.tag) { @@ -217,6 +217,9 @@ function unmountVNode(vNode: VNode): void { unmountDomComponents(vNode); break; } + default: { + break; + } } } @@ -413,6 +416,9 @@ function submitUpdate(vNode: VNode): void { listenToPromise(vNode); break; } + default: { + break; + } } } diff --git a/libs/horizon/src/renderer/submit/Submit.ts b/libs/horizon/src/renderer/submit/Submit.ts index 4ea6d47c..f0e8982a 100644 --- a/libs/horizon/src/renderer/submit/Submit.ts +++ b/libs/horizon/src/renderer/submit/Submit.ts @@ -41,63 +41,6 @@ const LOOPING_UPDATE_LIMIT = 50; let loopingUpdateCount = 0; let lastRoot: VNode | null = null; -export function submitToRender(treeRoot) { - treeRoot.shouldUpdate = treeRoot.childShouldUpdate; - // 置空task,让才能加入新的render任务 - treeRoot.task = null; - - const startVNode = getStartVNode(); - - if (FlagUtils.hasAnyFlag(startVNode)) { - // 把自己加上 - if (startVNode.dirtyNodes === null) { - startVNode.dirtyNodes = [startVNode]; - } else { - startVNode.dirtyNodes.push(startVNode); - } - } - - const dirtyNodes = startVNode.dirtyNodes; - if (dirtyNodes !== null && dirtyNodes.length) { - const preMode = copyExecuteMode(); - changeMode(InRender, true); - - prepareForSubmit(); - // before submit阶段 - beforeSubmit(dirtyNodes); - - // submit阶段 - submit(dirtyNodes); - - resetAfterSubmit(); - - // after submit阶段 - afterSubmit(dirtyNodes); - - setExecuteMode(preMode); - dirtyNodes.length = 0; - startVNode.dirtyNodes = null; - } - - if (isSchedulingEffects()) { - setSchedulingEffects(false); - } - - // 统计root同步重渲染的次数,如果太多可能是无线循环 - countLoopingUpdate(treeRoot); - - // 在退出`submit` 之前始终调用此函数,以确保任何已计划在此根上执行的update被执行。 - tryRenderFromRoot(treeRoot); - - if (rootThrowError) { - const error = rootThrowError; - rootThrowError = null; - throw error; - } - - return null; -} - function beforeSubmit(dirtyNodes: Array) { let node; const nodesLength = dirtyNodes.length; @@ -214,3 +157,60 @@ export function checkLoopingUpdateLimit() { ); } } + +export function submitToRender(treeRoot) { + treeRoot.shouldUpdate = treeRoot.childShouldUpdate; + // 置空task,让才能加入新的render任务 + treeRoot.task = null; + + const startVNode = getStartVNode(); + + if (FlagUtils.hasAnyFlag(startVNode)) { + // 把自己加上 + if (startVNode.dirtyNodes === null) { + startVNode.dirtyNodes = [startVNode]; + } else { + startVNode.dirtyNodes.push(startVNode); + } + } + + const dirtyNodes = startVNode.dirtyNodes; + if (dirtyNodes !== null && dirtyNodes.length) { + const preMode = copyExecuteMode(); + changeMode(InRender, true); + + prepareForSubmit(); + // before submit阶段 + beforeSubmit(dirtyNodes); + + // submit阶段 + submit(dirtyNodes); + + resetAfterSubmit(); + + // after submit阶段 + afterSubmit(dirtyNodes); + + setExecuteMode(preMode); + dirtyNodes.length = 0; + startVNode.dirtyNodes = null; + } + + if (isSchedulingEffects()) { + setSchedulingEffects(false); + } + + // 统计root同步重渲染的次数,如果太多可能是无线循环 + countLoopingUpdate(treeRoot); + + // 在退出`submit` 之前始终调用此函数,以确保任何已计划在此根上执行的update被执行。 + tryRenderFromRoot(treeRoot); + + if (rootThrowError) { + const error = rootThrowError; + rootThrowError = null; + throw error; + } + + return null; +} diff --git a/libs/horizon/src/renderer/taskExecutor/BrowserAsync.ts b/libs/horizon/src/renderer/taskExecutor/BrowserAsync.ts index 6da28613..cccef59d 100644 --- a/libs/horizon/src/renderer/taskExecutor/BrowserAsync.ts +++ b/libs/horizon/src/renderer/taskExecutor/BrowserAsync.ts @@ -27,6 +27,14 @@ export function isOverTime() { return false; } +function asyncCall() { + if (isTestRuntime) { + setTimeout(callRenderTasks, 0); + } else { + port2.postMessage(null); + } +} + // 1、设置deadline;2、回调TaskExecutor传过来的browserCallback const callRenderTasks = () => { if (browserCallback === null) { @@ -61,14 +69,6 @@ if (typeof MessageChannel === 'function') { isTestRuntime = true; } -function asyncCall() { - if (isTestRuntime) { - setTimeout(callRenderTasks, 0); - } else { - port2.postMessage(null); - } -} - export function requestBrowserCallback(callback) { browserCallback = callback; diff --git a/libs/horizon/src/renderer/taskExecutor/RenderQueue.ts b/libs/horizon/src/renderer/taskExecutor/RenderQueue.ts index 0fe3e533..729dfbc8 100644 --- a/libs/horizon/src/renderer/taskExecutor/RenderQueue.ts +++ b/libs/horizon/src/renderer/taskExecutor/RenderQueue.ts @@ -27,16 +27,6 @@ let callingQueueTask: any | null = null; // 防止重入 let isCallingRenderQueue = false; -export function callRenderQueueImmediate() { - if (callingQueueTask !== null) { - // 取消异步调度 - cancelTask(callingQueueTask); - callingQueueTask = null; - } - - callRenderQueue(); -} - // 执行render回调 function callRenderQueue() { if (!isCallingRenderQueue && renderQueue !== null) { @@ -58,6 +48,16 @@ function callRenderQueue() { } } +export function callRenderQueueImmediate() { + if (callingQueueTask !== null) { + // 取消异步调度 + cancelTask(callingQueueTask); + callingQueueTask = null; + } + + callRenderQueue(); +} + export function pushRenderCallback(callback: RenderCallback) { if (renderQueue === null) { renderQueue = [callback]; diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 4214e928..40ba0554 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -200,6 +200,8 @@ export class VNode { break; case Profiler: break; + default: + break; } } } diff --git a/libs/horizon/src/renderer/vnode/VNodeCreator.ts b/libs/horizon/src/renderer/vnode/VNodeCreator.ts index 6b96e49e..73944854 100644 --- a/libs/horizon/src/renderer/vnode/VNodeCreator.ts +++ b/libs/horizon/src/renderer/vnode/VNodeCreator.ts @@ -194,6 +194,8 @@ export function createVNode(tag: VNodeTag | string, ...secondArg) { vNode.updates = []; break; + default: + break; } return vNode; diff --git a/libs/horizon/src/renderer/vnode/VNodeUtils.ts b/libs/horizon/src/renderer/vnode/VNodeUtils.ts index 7e55f48d..d9bb0f50 100644 --- a/libs/horizon/src/renderer/vnode/VNodeUtils.ts +++ b/libs/horizon/src/renderer/vnode/VNodeUtils.ts @@ -169,13 +169,6 @@ export function findDOMByClassInst(inst) { return domVNode !== null ? domVNode.realNode : null; } -// 判断dom树是否已经挂载 -export function isMounted(vNode: VNode) { - const rootNode = getTreeRootVNode(vNode); - // 如果根节点是 Dom 类型节点,表示已经挂载 - return rootNode.tag === TreeRoot; -} - function getTreeRootVNode(vNode) { let node = vNode; while (node.parent) { @@ -184,6 +177,13 @@ function getTreeRootVNode(vNode) { return node; } +// 判断dom树是否已经挂载 +export function isMounted(vNode: VNode) { + const rootNode = getTreeRootVNode(vNode); + // 如果根节点是 Dom 类型节点,表示已经挂载 + return rootNode.tag === TreeRoot; +} + // 找到相邻的DOM export function getSiblingDom(vNode: VNode): Element | null { let node: VNode = vNode; From 8752ba53c5b4ddf26dddc2c9dadc625201b292dc Mon Sep 17 00:00:00 2001 From: * <*> Date: Tue, 18 Apr 2023 10:08:43 +0800 Subject: [PATCH 2/7] Match-id-5ec5b8bb1e9670f83af42904d6795648595acd4a --- libs/horizon/src/event/HorizonEventMain.ts | 2 +- libs/horizon/src/renderer/ErrorHandler.ts | 2 +- libs/horizon/src/renderer/render/BaseComponent.ts | 2 +- libs/horizon/src/renderer/render/SuspenseComponent.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/horizon/src/event/HorizonEventMain.ts b/libs/horizon/src/event/HorizonEventMain.ts index 77f22988..5c0ae747 100644 --- a/libs/horizon/src/event/HorizonEventMain.ts +++ b/libs/horizon/src/event/HorizonEventMain.ts @@ -45,7 +45,7 @@ function shouldTriggerChangeEvent(targetDom, evtName) { const { type } = targetDom; const domTag = getDomTag(targetDom); - if (domTag === 'select' || domTag === 'input' && type === 'file') { + if (domTag === 'select' || (domTag === 'input' && type === 'file')) { return evtName === 'change'; } else if (domTag === 'input' && (type === 'checkbox' || type === 'radio')) { if (evtName === 'click') { diff --git a/libs/horizon/src/renderer/ErrorHandler.ts b/libs/horizon/src/renderer/ErrorHandler.ts index efcba91d..7ba23cec 100644 --- a/libs/horizon/src/renderer/ErrorHandler.ts +++ b/libs/horizon/src/renderer/ErrorHandler.ts @@ -110,7 +110,7 @@ export function handleRenderThrowError(sourceVNode: VNode, error: any) { if ( (vNode.flags & DidCapture) === InitFlag && (typeof ctor.getDerivedStateFromError === 'function' || - instance !== null && typeof instance.componentDidCatch === 'function') + (instance !== null && typeof instance.componentDidCatch === 'function')) ) { FlagUtils.markShouldCapture(vNode); diff --git a/libs/horizon/src/renderer/render/BaseComponent.ts b/libs/horizon/src/renderer/render/BaseComponent.ts index 45b5be71..27449235 100644 --- a/libs/horizon/src/renderer/render/BaseComponent.ts +++ b/libs/horizon/src/renderer/render/BaseComponent.ts @@ -73,7 +73,7 @@ export function captureVNode(processing: VNode): VNode | null { export function markRef(processing: VNode) { const ref = processing.ref; - if (processing.isCreated && ref !== null || !processing.isCreated && processing.oldRef !== ref) { + if ((processing.isCreated && ref !== null) || (!processing.isCreated && processing.oldRef !== ref)) { FlagUtils.markRef(processing); } } diff --git a/libs/horizon/src/renderer/render/SuspenseComponent.ts b/libs/horizon/src/renderer/render/SuspenseComponent.ts index a73644c7..82c04dee 100644 --- a/libs/horizon/src/renderer/render/SuspenseComponent.ts +++ b/libs/horizon/src/renderer/render/SuspenseComponent.ts @@ -151,7 +151,7 @@ export function bubbleRender(processing: VNode) { const { childStatus, oldChildStatus } = processing.suspenseState; if ( childStatus === SuspenseChildStatus.ShowFallback || - !processing.isCreated && oldChildStatus === SuspenseChildStatus.ShowFallback + (!processing.isCreated && oldChildStatus === SuspenseChildStatus.ShowFallback) ) { FlagUtils.markUpdate(processing); } From f651733b456a1ce71bf9e0815c11cae42c93246b Mon Sep 17 00:00:00 2001 From: * <*> Date: Tue, 18 Apr 2023 10:30:01 +0800 Subject: [PATCH 3/7] Match-id-8498afa1809522a435c0673515cbe56bd60a7df3 --- libs/horizon/src/horizonx/adapters/redux.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libs/horizon/src/horizonx/adapters/redux.ts b/libs/horizon/src/horizonx/adapters/redux.ts index 46d4b663..97b7a483 100644 --- a/libs/horizon/src/horizonx/adapters/redux.ts +++ b/libs/horizon/src/horizonx/adapters/redux.ts @@ -71,7 +71,9 @@ function mergeData(state, data) { if (typeof data === 'object' && typeof state?.stateWrapper === 'object') { Object.keys(state.stateWrapper).forEach(key => { - if (!Object.prototype.hasOwnProperty.call(data, key)) delete state.stateWrapper[key]; + if (!Object.prototype.hasOwnProperty.call(data, key)) { + delete state.stateWrapper[key]; + } }); Object.entries(data).forEach(([key, value]) => { From 28f0a2b9b6560a54e09ef764bffbc89eb981c0b0 Mon Sep 17 00:00:00 2001 From: * <*> Date: Tue, 18 Apr 2023 11:45:36 +0800 Subject: [PATCH 4/7] Match-id-6d0d90f21208e053a7c407bacfa5a3862d7806fd --- .../dom/DOMPropertiesHandler/StyleHandler.ts | 40 ++-- libs/horizon/src/event/EventBinding.ts | 16 +- libs/horizon/src/external/TestUtil.ts | 28 +-- libs/horizon/src/horizonx/devtools/index.ts | 31 +-- .../src/horizonx/proxy/ProxyHandler.ts | 8 +- .../proxy/handlers/ArrayProxyHandler.ts | 68 +++--- .../src/horizonx/proxy/handlers/MapProxy.ts | 136 ++++++------ .../proxy/handlers/ObjectProxyHandler.ts | 42 ++-- .../src/horizonx/proxy/handlers/SetProxy.ts | 91 ++++---- .../src/horizonx/store/StoreHandler.ts | 188 ++++++++-------- libs/horizon/src/renderer/TreeBuilder.ts | 68 +++--- .../src/renderer/diff/nodeDiffComparator.ts | 26 +-- .../src/renderer/submit/LifeCycleHandler.ts | 202 +++++++++--------- 13 files changed, 474 insertions(+), 470 deletions(-) diff --git a/libs/horizon/src/dom/DOMPropertiesHandler/StyleHandler.ts b/libs/horizon/src/dom/DOMPropertiesHandler/StyleHandler.ts index c3954fa1..3c3a9a24 100644 --- a/libs/horizon/src/dom/DOMPropertiesHandler/StyleHandler.ts +++ b/libs/horizon/src/dom/DOMPropertiesHandler/StyleHandler.ts @@ -13,6 +13,26 @@ * See the Mulan PSL v2 for more details. */ +/** + * 不需要加长度单位的 css 属性 + */ +const noUnitCSS = [ + 'animationIterationCount', + 'columnCount', + 'columns', + 'gridArea', + 'fontWeight', + 'lineClamp', + 'lineHeight', + 'opacity', + 'order', + 'orphans', + 'tabSize', + 'widows', + 'zIndex', + 'zoom', +]; + function isNeedUnitCSS(styleName: string) { return !( noUnitCSS.includes(styleName) || @@ -62,23 +82,3 @@ export function setStyles(dom, styles) { } }); } - -/** - * 不需要加长度单位的 css 属性 - */ -const noUnitCSS = [ - 'animationIterationCount', - 'columnCount', - 'columns', - 'gridArea', - 'fontWeight', - 'lineClamp', - 'lineHeight', - 'opacity', - 'order', - 'orphans', - 'tabSize', - 'widows', - 'zIndex', - 'zoom', -]; diff --git a/libs/horizon/src/event/EventBinding.ts b/libs/horizon/src/event/EventBinding.ts index e9d88308..25c52b90 100644 --- a/libs/horizon/src/event/EventBinding.ts +++ b/libs/horizon/src/event/EventBinding.ts @@ -56,6 +56,14 @@ function listenToNativeEvent(nativeEvtName: string, delegatedElement: Element, i return listener; } +// 是否捕获事件 +function isCaptureEvent(horizonEventName) { + if (horizonEventName === 'onLostPointerCapture' || horizonEventName === 'onGotPointerCapture') { + return false; + } + return horizonEventName.slice(-7) === 'Capture'; +} + // 事件懒委托,当用户定义事件后,再进行委托到根节点 export function lazyDelegateOnRoot(currentRoot: VNode, eventName: string) { currentRoot.delegatedEvents.add(eventName); @@ -94,14 +102,6 @@ function getNativeEvtName(horizonEventName, capture) { return nativeName.toLowerCase(); } -// 是否捕获事件 -function isCaptureEvent(horizonEventName) { - if (horizonEventName === 'onLostPointerCapture' || horizonEventName === 'onGotPointerCapture') { - return false; - } - return horizonEventName.slice(-7) === 'Capture'; -} - // 封装监听函数 function getWrapperListener(horizonEventName, nativeEvtName, targetElement, listener) { return event => { diff --git a/libs/horizon/src/external/TestUtil.ts b/libs/horizon/src/external/TestUtil.ts index dc9e3dbd..3b5b24ca 100644 --- a/libs/horizon/src/external/TestUtil.ts +++ b/libs/horizon/src/external/TestUtil.ts @@ -23,6 +23,20 @@ interface Thenable { then(resolve: (val?: any) => void, reject: (err: any) => void): void; } +// 防止死循环 +const LOOPING_LIMIT = 50; +let loopingCount = 0; +function callRenderQueue() { + callRenderQueueImmediate(); + + while (hasAsyncEffects() && loopingCount < LOOPING_LIMIT) { + loopingCount++; + runAsyncEffects(); + // effects可能产生刷新任务,这里再执行一次 + callRenderQueueImmediate(); + } +} + // act用于测试,作用是:如果fun触发了刷新(包含了异步刷新),可以保证在act后面的代码是在刷新完成后才执行。 function act(fun: () => void | Thenable): Thenable { const funRet = asyncUpdates(fun); @@ -62,20 +76,6 @@ function act(fun: () => void | Thenable): Thenable { } } -// 防止死循环 -const LOOPING_LIMIT = 50; -let loopingCount = 0; -function callRenderQueue() { - callRenderQueueImmediate(); - - while (hasAsyncEffects() && loopingCount < LOOPING_LIMIT) { - loopingCount++; - runAsyncEffects(); - // effects可能产生刷新任务,这里再执行一次 - callRenderQueueImmediate(); - } -} - export { act }; diff --git a/libs/horizon/src/horizonx/devtools/index.ts b/libs/horizon/src/horizonx/devtools/index.ts index 23bb6000..64a0f189 100644 --- a/libs/horizon/src/horizonx/devtools/index.ts +++ b/libs/horizon/src/horizonx/devtools/index.ts @@ -10,21 +10,6 @@ export function isPanelActive() { return window['__HORIZON_DEV_HOOK__']; } -// serializes store and creates expanded object with baked-in containing current computed values -function makeStoreSnapshot({ type, data }) { - const expanded = {}; - Object.keys(data.store.$c).forEach(key => { - expanded[key] = data.store[key]; - }); - data.store.expanded = expanded; - const snapshot = makeProxySnapshot({ - data, - type, - sessionId, - }); - return snapshot; -} - // safely serializes variables containing values wrapped in Proxy object function getType(value) { if (!value) return 'nullish'; @@ -39,6 +24,7 @@ function getType(value) { if (typeof value === 'object') return 'object'; return 'primitive'; } + function makeProxySnapshot(obj, visited: any[] = []) { const type = getType(obj); let clone; @@ -112,6 +98,21 @@ function makeProxySnapshot(obj, visited: any[] = []) { } } +// serializes store and creates expanded object with baked-in containing current computed values +function makeStoreSnapshot({ type, data }) { + const expanded = {}; + Object.keys(data.store.$c).forEach(key => { + expanded[key] = data.store[key]; + }); + data.store.expanded = expanded; + const snapshot = makeProxySnapshot({ + data, + type, + sessionId, + }); + return snapshot; +} + export const devtools = { // returns vNode id from horizon devtools getVNodeId: vNode => { diff --git a/libs/horizon/src/horizonx/proxy/ProxyHandler.ts b/libs/horizon/src/horizonx/proxy/ProxyHandler.ts index d165675e..be83bee5 100644 --- a/libs/horizon/src/horizonx/proxy/ProxyHandler.ts +++ b/libs/horizon/src/horizonx/proxy/ProxyHandler.ts @@ -27,6 +27,10 @@ const proxyMap = new WeakMap(); export const hookObserverMap = new WeakMap(); +export function getObserver(rawObj: any): Observer { + return rawObj[OBSERVER_KEY]; +} + export function createProxy(rawObj: any, isHookObserver = true, listener: { current: (...args) => any }): any { // 不是对象(是原始数据类型)不用代理 if (!(rawObj && isObject(rawObj))) { @@ -89,7 +93,3 @@ export function createProxy(rawObj: any, isHookObserver = true, listener: { curr return proxyObj; } - -export function getObserver(rawObj: any): Observer { - return rawObj[OBSERVER_KEY]; -} diff --git a/libs/horizon/src/horizonx/proxy/handlers/ArrayProxyHandler.ts b/libs/horizon/src/horizonx/proxy/handlers/ArrayProxyHandler.ts index 3938ecab..669f560a 100644 --- a/libs/horizon/src/horizonx/proxy/handlers/ArrayProxyHandler.ts +++ b/libs/horizon/src/horizonx/proxy/handlers/ArrayProxyHandler.ts @@ -19,6 +19,40 @@ import { resolveMutation } from '../../CommonUtils'; import { isPanelActive } from '../../devtools'; import { OBSERVER_KEY } from '../../Constants'; +function set(rawObj: any[], key: string, value: any, receiver: any) { + const oldValue = rawObj[key]; + const oldLength = rawObj.length; + const newValue = value; + + const oldArray = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null; + + const ret = Reflect.set(rawObj, key, newValue, receiver); + + const newLength = rawObj.length; + const observer = getObserver(rawObj); + + const mutation = isPanelActive() ? resolveMutation(oldArray, rawObj) : resolveMutation(null, rawObj); + + if (!isSame(newValue, oldValue)) { + // 值不一样,触发监听器 + if (observer.watchers?.[key]) { + observer.watchers[key].forEach(cb => { + cb(key, oldValue, newValue, mutation); + }); + } + + // 触发属性变化 + observer.setProp(key, mutation); + } + + if (oldLength !== newLength) { + // 触发数组的大小变化 + observer.setProp('length', mutation); + } + + return ret; +} + export function createArrayProxy(rawObj: any[], listener: { current: (...args) => any }): any[] { let listeners = [] as ((...args) => void)[]; @@ -118,37 +152,3 @@ export function createArrayProxy(rawObj: any[], listener: { current: (...args) = return new Proxy(rawObj, handle); } - -function set(rawObj: any[], key: string, value: any, receiver: any) { - const oldValue = rawObj[key]; - const oldLength = rawObj.length; - const newValue = value; - - const oldArray = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null; - - const ret = Reflect.set(rawObj, key, newValue, receiver); - - const newLength = rawObj.length; - const observer = getObserver(rawObj); - - const mutation = isPanelActive() ? resolveMutation(oldArray, rawObj) : resolveMutation(null, rawObj); - - if (!isSame(newValue, oldValue)) { - // 值不一样,触发监听器 - if (observer.watchers?.[key]) { - observer.watchers[key].forEach(cb => { - cb(key, oldValue, newValue, mutation); - }); - } - - // 触发属性变化 - observer.setProp(key, mutation); - } - - if (oldLength !== newLength) { - // 触发数组的大小变化 - observer.setProp('length', mutation); - } - - return ret; -} diff --git a/libs/horizon/src/horizonx/proxy/handlers/MapProxy.ts b/libs/horizon/src/horizonx/proxy/handlers/MapProxy.ts index d5ed5d37..dd51f07f 100644 --- a/libs/horizon/src/horizonx/proxy/handlers/MapProxy.ts +++ b/libs/horizon/src/horizonx/proxy/handlers/MapProxy.ts @@ -25,48 +25,6 @@ export function createMapProxy(rawObj: Object, hookObserver = true, listener: { let oldData: [any, any][] = []; let proxies = new Map(); - function get(rawObj: { size: number }, key: any, receiver: any): any { - if (key === 'size') { - return size(rawObj); - } - - if (key === 'get') { - return getFun.bind(null, rawObj); - } - - if (Object.prototype.hasOwnProperty.call(handler, key)) { - const value = Reflect.get(handler, key, receiver); - return value.bind(null, rawObj); - } - - if (key === 'watch') { - const observer = getObserver(rawObj); - - return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => { - if (!observer.watchers[prop]) { - observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[]; - } - observer.watchers[prop].push(handler); - return () => { - observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler); - }; - }; - } - - if (key === 'addListener') { - return listener => { - listeners.push(listener); - }; - } - - if (key === 'removeListener') { - return listener => { - listeners = listeners.filter(item => item != listener); - }; - } - - return Reflect.get(rawObj, key, receiver); - } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function getFun(rawObj: { get: (key: any) => any; has: (key: any) => boolean }, key: any) { const keyProxy = rawObj.has(key) ? key : proxies.get(key); @@ -211,31 +169,6 @@ export function createMapProxy(rawObj: Object, hookObserver = true, listener: { return false; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - function size(rawObj: { size: number }) { - const observer = getObserver(rawObj); - observer.useProp(COLLECTION_CHANGE); - return rawObj.size; - } - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - function keys(rawObj: { keys: () => { next: () => { value: any; done: boolean } } }) { - return wrapIterator(rawObj, rawObj.keys(), 'keys'); - } - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - function values(rawObj: { values: () => { next: () => { value: any; done: boolean } } }) { - return wrapIterator(rawObj, rawObj.values(), 'values'); - } - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - function entries(rawObj: { entries: () => { next: () => { value: any; done: boolean } } }) { - return wrapIterator(rawObj, rawObj.entries(), 'entries'); - } - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - function forOf(rawObj: { - entries: () => { next: () => { value: any; done: boolean } }; - values: () => { next: () => { value: any; done: boolean } }; - }) { - return wrapIterator(rawObj, rawObj.entries(), 'entries'); - } - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function forEach( rawObj: { forEach: (callback: (value: any, key: any) => void) => void }, callback: (valProxy: any, keyProxy: any, rawObj: any) => void @@ -354,6 +287,75 @@ export function createMapProxy(rawObj: Object, hookObserver = true, listener: { }, }; } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function size(rawObj: { size: number }) { + const observer = getObserver(rawObj); + observer.useProp(COLLECTION_CHANGE); + return rawObj.size; + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function keys(rawObj: { keys: () => { next: () => { value: any; done: boolean } } }) { + return wrapIterator(rawObj, rawObj.keys(), 'keys'); + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function values(rawObj: { values: () => { next: () => { value: any; done: boolean } } }) { + return wrapIterator(rawObj, rawObj.values(), 'values'); + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function entries(rawObj: { entries: () => { next: () => { value: any; done: boolean } } }) { + return wrapIterator(rawObj, rawObj.entries(), 'entries'); + } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + function forOf(rawObj: { + entries: () => { next: () => { value: any; done: boolean } }; + values: () => { next: () => { value: any; done: boolean } }; + }) { + return wrapIterator(rawObj, rawObj.entries(), 'entries'); + } + + function get(rawObj: { size: number }, key: any, receiver: any): any { + if (key === 'size') { + return size(rawObj); + } + + if (key === 'get') { + return getFun.bind(null, rawObj); + } + + if (Object.prototype.hasOwnProperty.call(handler, key)) { + const value = Reflect.get(handler, key, receiver); + return value.bind(null, rawObj); + } + + if (key === 'watch') { + const observer = getObserver(rawObj); + + return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => { + if (!observer.watchers[prop]) { + observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[]; + } + observer.watchers[prop].push(handler); + return () => { + observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler); + }; + }; + } + + if (key === 'addListener') { + return listener => { + listeners.push(listener); + }; + } + + if (key === 'removeListener') { + return listener => { + listeners = listeners.filter(item => item != listener); + }; + } + + return Reflect.get(rawObj, key, receiver); + } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// const handler = { get, diff --git a/libs/horizon/src/horizonx/proxy/handlers/ObjectProxyHandler.ts b/libs/horizon/src/horizonx/proxy/handlers/ObjectProxyHandler.ts index f1ac97b7..0afa6583 100644 --- a/libs/horizon/src/horizonx/proxy/handlers/ObjectProxyHandler.ts +++ b/libs/horizon/src/horizonx/proxy/handlers/ObjectProxyHandler.ts @@ -18,6 +18,27 @@ import { createProxy, getObserver, hookObserverMap } from '../ProxyHandler'; import { OBSERVER_KEY } from '../../Constants'; import { isPanelActive } from '../../devtools'; +function set(rawObj: object, key: string, value: any, receiver: any): boolean { + const oldObject = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null; + const observer = getObserver(rawObj); + + const oldValue = rawObj[key]; + const newValue = value; + + const ret = Reflect.set(rawObj, key, newValue, receiver); + const mutation = isPanelActive() ? resolveMutation(oldObject, rawObj) : resolveMutation(null, rawObj); + + if (!isSame(newValue, oldValue)) { + if (observer.watchers?.[key]) { + observer.watchers[key].forEach(cb => { + cb(key, oldValue, newValue, mutation); + }); + } + observer.setProp(key, mutation); + } + return ret; +} + export function createObjectProxy( rawObj: T, singleLevel = false, @@ -99,24 +120,3 @@ export function createObjectProxy( return proxy; } - -function set(rawObj: object, key: string, value: any, receiver: any): boolean { - const oldObject = isPanelActive() ? JSON.parse(JSON.stringify(rawObj)) : null; - const observer = getObserver(rawObj); - - const oldValue = rawObj[key]; - const newValue = value; - - const ret = Reflect.set(rawObj, key, newValue, receiver); - const mutation = isPanelActive() ? resolveMutation(oldObject, rawObj) : resolveMutation(null, rawObj); - - if (!isSame(newValue, oldValue)) { - if (observer.watchers?.[key]) { - observer.watchers[key].forEach(cb => { - cb(key, oldValue, newValue, mutation); - }); - } - observer.setProp(key, mutation); - } - return ret; -} diff --git a/libs/horizon/src/horizonx/proxy/handlers/SetProxy.ts b/libs/horizon/src/horizonx/proxy/handlers/SetProxy.ts index 6a4401fa..b0409ad6 100644 --- a/libs/horizon/src/horizonx/proxy/handlers/SetProxy.ts +++ b/libs/horizon/src/horizonx/proxy/handlers/SetProxy.ts @@ -26,43 +26,6 @@ export function createSetProxy( let listeners: ((mutation) => {})[] = []; let proxies = new WeakMap(); - function get(rawObj: { size: number }, key: any, receiver: any): any { - if (Object.prototype.hasOwnProperty.call(handler, key)) { - const value = Reflect.get(handler, key, receiver); - return value.bind(null, rawObj); - } - - if (key === 'size') { - return size(rawObj); - } - - if (key === 'addListener') { - return listener => { - listeners.push(listener); - }; - } - - if (key === 'removeListener') { - return listener => { - listeners = listeners.filter(item => item != listener); - }; - } - if (key === 'watch') { - const observer = getObserver(rawObj); - - return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => { - if (!observer.watchers[prop]) { - observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[]; - } - observer.watchers[prop].push(handler); - return () => { - observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler); - }; - }; - } - return Reflect.get(rawObj, key, receiver); - } - ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Set的add方法 function add(rawObj: { add: (any) => void; has: (any) => boolean; values: () => any[] }, value: any): Object { if (!rawObj.has(proxies.get(value))) { @@ -166,17 +129,43 @@ export function createSetProxy( return rawObj.size; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - function keys(rawObj: { keys: () => { next: () => { value: any; done: boolean } } }) { - return wrapIterator(rawObj, rawObj.keys()); - } + function get(rawObj: { size: number }, key: any, receiver: any): any { + if (Object.prototype.hasOwnProperty.call(handler, key)) { + const value = Reflect.get(handler, key, receiver); + return value.bind(null, rawObj); + } - function values(rawObj: { values: () => { next: () => { value: any; done: boolean } } }) { - return wrapIterator(rawObj, rawObj.values()); - } + if (key === 'size') { + return size(rawObj); + } - function entries(rawObj: { entries: () => { next: () => { value: any; done: boolean } } }) { - return wrapIterator(rawObj, rawObj.entries()); + if (key === 'addListener') { + return listener => { + listeners.push(listener); + }; + } + + if (key === 'removeListener') { + return listener => { + listeners = listeners.filter(item => item != listener); + }; + } + if (key === 'watch') { + const observer = getObserver(rawObj); + + return (prop: any, handler: (key: string, oldValue: any, newValue: any) => void) => { + if (!observer.watchers[prop]) { + observer.watchers[prop] = [] as ((key: string, oldValue: any, newValue: any) => void)[]; + } + observer.watchers[prop].push(handler); + return () => { + observer.watchers[prop] = observer.watchers[prop].filter(cb => cb !== handler); + }; + }; + } + return Reflect.get(rawObj, key, receiver); } + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function wrapIterator(rawObj: Object, rawIt: { next: () => { value: any; done: boolean } }) { const observer = getObserver(rawObj); @@ -224,6 +213,18 @@ export function createSetProxy( }; } + function keys(rawObj: { keys: () => { next: () => { value: any; done: boolean } } }) { + return wrapIterator(rawObj, rawObj.keys()); + } + + function values(rawObj: { values: () => { next: () => { value: any; done: boolean } } }) { + return wrapIterator(rawObj, rawObj.values()); + } + + function entries(rawObj: { entries: () => { next: () => { value: any; done: boolean } } }) { + return wrapIterator(rawObj, rawObj.entries()); + } + function forOf(rawObj: { entries: () => { next: () => { value: any; done: boolean } }; values: () => { next: () => { value: any; done: boolean } }; diff --git a/libs/horizon/src/horizonx/store/StoreHandler.ts b/libs/horizon/src/horizonx/store/StoreHandler.ts index 781deef4..2afd33f2 100644 --- a/libs/horizon/src/horizonx/store/StoreHandler.ts +++ b/libs/horizon/src/horizonx/store/StoreHandler.ts @@ -53,6 +53,100 @@ const idGenerator = { const storeMap = new Map>(); +// 通过该方法执行store.$queue中的action +function tryNextAction(storeObj, proxyObj, config, plannedActions) { + if (!plannedActions.length) { + if (proxyObj.$pending) { + const timestamp = Date.now(); + const duration = timestamp - proxyObj.$pending; + proxyObj.$pending = false; + devtools.emit(QUEUE_FINISHED, { + store: storeObj, + endedAt: timestamp, + duration, + }); + } + return; + } + + const nextAction = plannedActions.shift()!; + const result = config.actions + ? config.actions[nextAction.action].bind(storeObj, proxyObj)(...nextAction.payload) + : undefined; + + if (isPromise(result)) { + result.then(value => { + nextAction.resolve(value); + tryNextAction(storeObj, proxyObj, config, plannedActions); + }); + } else { + nextAction.resolve(result); + tryNextAction(storeObj, proxyObj, config, plannedActions); + } +} + +// 删除Observers中保存的这个VNode的相关数据 +export function clearVNodeObservers(vNode: VNode) { + if (!vNode.observers) { + return; + } + + vNode.observers.forEach(observer => { + observer.clearByVNode(vNode); + }); + + vNode.observers.clear(); +} + +// 注册VNode销毁时的清理动作 +function registerDestroyFunction() { + const processingVNode = getProcessingVNode(); + + // 获取不到当前运行的VNode,说明不在组件中运行,属于非法场景 + if (!processingVNode) { + return; + } + + if (!processingVNode.observers) { + processingVNode.observers = new Set(); + } + + // 函数组件 + if (processingVNode.tag === FunctionComponent) { + const vNodeRef = useRef(processingVNode); + + useEffect(() => { + return () => { + clearVNodeObservers(vNodeRef.current); + vNodeRef.current.observers = null; + }; + }, []); + } else if (processingVNode.tag === ClassComponent) { + // 类组件 + if (!processingVNode.classComponentWillUnmount) { + processingVNode.classComponentWillUnmount = vNode => { + clearVNodeObservers(vNode); + vNode.observers = null; + }; + } + } +} + +// createStore返回的是一个getStore的函数,这个函数必须要在组件(函数/类组件)里面被执行,因为要注册VNode销毁时的清理动作 +function createGetStore, C extends UserComputedValues>( + storeObj: StoreObj +): () => StoreObj { + const getStore = () => { + if (!storeObj.$config.options?.isReduxAdapter) { + registerDestroyFunction(); + } + + return storeObj; + }; + + return getStore; +} + export function createStore, C extends UserComputedValues>( config: StoreConfig ): () => StoreObj { @@ -216,100 +310,6 @@ export function createStore, C extend return createGetStore(storeObj); } -// 通过该方法执行store.$queue中的action -function tryNextAction(storeObj, proxyObj, config, plannedActions) { - if (!plannedActions.length) { - if (proxyObj.$pending) { - const timestamp = Date.now(); - const duration = timestamp - proxyObj.$pending; - proxyObj.$pending = false; - devtools.emit(QUEUE_FINISHED, { - store: storeObj, - endedAt: timestamp, - duration, - }); - } - return; - } - - const nextAction = plannedActions.shift()!; - const result = config.actions - ? config.actions[nextAction.action].bind(storeObj, proxyObj)(...nextAction.payload) - : undefined; - - if (isPromise(result)) { - result.then(value => { - nextAction.resolve(value); - tryNextAction(storeObj, proxyObj, config, plannedActions); - }); - } else { - nextAction.resolve(result); - tryNextAction(storeObj, proxyObj, config, plannedActions); - } -} - -// createStore返回的是一个getStore的函数,这个函数必须要在组件(函数/类组件)里面被执行,因为要注册VNode销毁时的清理动作 -function createGetStore, C extends UserComputedValues>( - storeObj: StoreObj -): () => StoreObj { - const getStore = () => { - if (!storeObj.$config.options?.isReduxAdapter) { - registerDestroyFunction(); - } - - return storeObj; - }; - - return getStore; -} - -// 删除Observers中保存的这个VNode的相关数据 -export function clearVNodeObservers(vNode: VNode) { - if (!vNode.observers) { - return; - } - - vNode.observers.forEach(observer => { - observer.clearByVNode(vNode); - }); - - vNode.observers.clear(); -} - -// 注册VNode销毁时的清理动作 -function registerDestroyFunction() { - const processingVNode = getProcessingVNode(); - - // 获取不到当前运行的VNode,说明不在组件中运行,属于非法场景 - if (!processingVNode) { - return; - } - - if (!processingVNode.observers) { - processingVNode.observers = new Set(); - } - - // 函数组件 - if (processingVNode.tag === FunctionComponent) { - const vNodeRef = useRef(processingVNode); - - useEffect(() => { - return () => { - clearVNodeObservers(vNodeRef.current); - vNodeRef.current.observers = null; - }; - }, []); - } else if (processingVNode.tag === ClassComponent) { - // 类组件 - if (!processingVNode.classComponentWillUnmount) { - processingVNode.classComponentWillUnmount = vNode => { - clearVNodeObservers(vNode); - vNode.observers = null; - }; - } - } -} - // 函数组件中使用的hook export function useStore, C extends UserComputedValues>( id: string diff --git a/libs/horizon/src/renderer/TreeBuilder.ts b/libs/horizon/src/renderer/TreeBuilder.ts index 7d37de2f..eb20b0ef 100644 --- a/libs/horizon/src/renderer/TreeBuilder.ts +++ b/libs/horizon/src/renderer/TreeBuilder.ts @@ -228,6 +228,40 @@ export function calcStartUpdateVNode(treeRoot: VNode) { return node; } +// 在局部更新时,从上到下恢复父节点的context和PortalStack +function recoverTreeContext(vNode: VNode) { + const contextProviders: VNode[] = []; + let parent = vNode.parent; + while (parent !== null) { + if (parent.tag === ContextProvider) { + contextProviders.unshift(parent); + } + if (parent.tag === DomPortal) { + pushCurrentRoot(parent); + } + parent = parent.parent; + } + + contextProviders.forEach(node => { + setContext(node, node.props.value); + }); +} + +// 在局部更新时,从下到上重置父节点的context +function resetTreeContext(vNode: VNode) { + let parent = vNode.parent; + + while (parent !== null) { + if (parent.tag === ContextProvider) { + resetContext(parent); + } + if (parent.tag === DomPortal) { + popCurrentRoot(); + } + parent = parent.parent; + } +} + // ============================== 深度遍历 ============================== function buildVNodeTree(treeRoot: VNode) { const preMode = copyExecuteMode(); @@ -299,40 +333,6 @@ function buildVNodeTree(treeRoot: VNode) { setExecuteMode(preMode); } -// 在局部更新时,从上到下恢复父节点的context和PortalStack -function recoverTreeContext(vNode: VNode) { - const contextProviders: VNode[] = []; - let parent = vNode.parent; - while (parent !== null) { - if (parent.tag === ContextProvider) { - contextProviders.unshift(parent); - } - if (parent.tag === DomPortal) { - pushCurrentRoot(parent); - } - parent = parent.parent; - } - - contextProviders.forEach(node => { - setContext(node, node.props.value); - }); -} - -// 在局部更新时,从下到上重置父节点的context -function resetTreeContext(vNode: VNode) { - let parent = vNode.parent; - - while (parent !== null) { - if (parent.tag === ContextProvider) { - resetContext(parent); - } - if (parent.tag === DomPortal) { - popCurrentRoot(); - } - parent = parent.parent; - } -} - // 总体任务入口 function renderFromRoot(treeRoot) { runAsyncEffects(); diff --git a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts index 56753063..61706bea 100644 --- a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts +++ b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts @@ -231,6 +231,19 @@ function getOldNodeFromMap(nodeMap: Map, newIdx: number, return null; } +// 设置vNode中的cIndex属性,cIndex是节点在children中的位置 +function setVNodesCIndex(startChild: VNode | null, startIdx: number) { + let node: VNode | null = startChild; + let idx = startIdx; + + while (node !== null) { + node.cIndex = idx; + markVNodePath(node); + node = node.next; + idx++; + } +} + // diff数组类型的节点,核心算法 function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newChildren: Array): VNode | null { let resultingFirstChild: VNode | null = null; @@ -478,19 +491,6 @@ function diffArrayNodesHandler(parentNode: VNode, firstChild: VNode | null, newC return resultingFirstChild; } -// 设置vNode中的cIndex属性,cIndex是节点在children中的位置 -function setVNodesCIndex(startChild: VNode | null, startIdx: number) { - let node: VNode | null = startChild; - let idx = startIdx; - - while (node !== null) { - node.cIndex = idx; - markVNodePath(node); - node = node.next; - idx++; - } -} - // 新节点是迭代器类型 function diffIteratorNodesHandler( parentNode: VNode, diff --git a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts index ebf73031..056f18f9 100644 --- a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts +++ b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts @@ -182,47 +182,6 @@ function detachRef(vNode: VNode, isOldRef?: boolean) { handleRef(vNode, ref, null); } -// 卸载一个vNode,不会递归 -function unmountVNode(vNode: VNode): void { - switch (vNode.tag) { - case FunctionComponent: - case ForwardRef: - case MemoComponent: { - callEffectRemove(vNode); - break; - } - case ClassComponent: { - detachRef(vNode); - - const instance = vNode.realNode; - // 当constructor中抛出异常时,instance会是null,这里判断一下instance是否为空 - // suspense打断时不需要触发WillUnmount - if (instance && typeof instance.componentWillUnmount === 'function' && !vNode.isSuspended) { - callComponentWillUnmount(vNode, instance); - } - - // HorizonX会在classComponentWillUnmount中清除对VNode的引入用 - if (vNode.classComponentWillUnmount) { - vNode.classComponentWillUnmount(vNode); - vNode.classComponentWillUnmount = null; - } - break; - } - case DomComponent: { - detachRef(vNode); - break; - } - case DomPortal: { - // 这里会递归 - unmountDomComponents(vNode); - break; - } - default: { - break; - } - } -} - // 卸载vNode,递归遍历子vNode function unmountNestedVNodes(vNode: VNode): void { travelVNodeTree( @@ -238,59 +197,6 @@ function unmountNestedVNodes(vNode: VNode): void { ); } -function submitAddition(vNode: VNode): void { - let parent = vNode.parent; - let parentDom; - let tag; - while (parent !== null) { - tag = parent.tag; - if (tag === DomComponent || tag === TreeRoot || tag === DomPortal) { - parentDom = parent.realNode; - break; - } - parent = parent.parent; - } - - if ((parent.flags & ResetText) === ResetText) { - // 在insert之前先reset - clearText(parentDom); - FlagUtils.removeFlag(parent, ResetText); - } - - if ((vNode.flags & DirectAddition) === DirectAddition) { - insertOrAppendPlacementNode(vNode, null, parentDom); - FlagUtils.removeFlag(vNode, DirectAddition); - return; - } - const before = getSiblingDom(vNode); - insertOrAppendPlacementNode(vNode, before, parentDom); -} - -function insertOrAppendPlacementNode(node: VNode, beforeDom: Element | null, parent: Element | Container): void { - const { tag, realNode } = node; - - if (isDomVNode(node)) { - insertDom(parent, realNode, beforeDom); - } else if (tag === DomPortal) { - // 这里不做处理,直接在portal中处理 - } else { - // 插入子节点们 - let child = node.child; - while (child !== null) { - insertOrAppendPlacementNode(child, beforeDom, parent); - child = child.next; - } - } -} - -function insertDom(parent, realNode, beforeDom) { - if (beforeDom) { - insertDomBefore(parent, realNode, beforeDom); - } else { - appendChildElement(parent, realNode); - } -} - // 遍历所有子节点:删除dom节点,detach ref 和 调用componentWillUnmount() function unmountDomComponents(vNode: VNode): void { let currentParentIsValid = false; @@ -342,6 +248,100 @@ function unmountDomComponents(vNode: VNode): void { ); } +// 卸载一个vNode,不会递归 +function unmountVNode(vNode: VNode): void { + switch (vNode.tag) { + case FunctionComponent: + case ForwardRef: + case MemoComponent: { + callEffectRemove(vNode); + break; + } + case ClassComponent: { + detachRef(vNode); + + const instance = vNode.realNode; + // 当constructor中抛出异常时,instance会是null,这里判断一下instance是否为空 + // suspense打断时不需要触发WillUnmount + if (instance && typeof instance.componentWillUnmount === 'function' && !vNode.isSuspended) { + callComponentWillUnmount(vNode, instance); + } + + // HorizonX会在classComponentWillUnmount中清除对VNode的引入用 + if (vNode.classComponentWillUnmount) { + vNode.classComponentWillUnmount(vNode); + vNode.classComponentWillUnmount = null; + } + break; + } + case DomComponent: { + detachRef(vNode); + break; + } + case DomPortal: { + // 这里会递归 + unmountDomComponents(vNode); + break; + } + default: { + break; + } + } +} + +function insertDom(parent, realNode, beforeDom) { + if (beforeDom) { + insertDomBefore(parent, realNode, beforeDom); + } else { + appendChildElement(parent, realNode); + } +} + +function insertOrAppendPlacementNode(node: VNode, beforeDom: Element | null, parent: Element | Container): void { + const { tag, realNode } = node; + + if (isDomVNode(node)) { + insertDom(parent, realNode, beforeDom); + } else if (tag === DomPortal) { + // 这里不做处理,直接在portal中处理 + } else { + // 插入子节点们 + let child = node.child; + while (child !== null) { + insertOrAppendPlacementNode(child, beforeDom, parent); + child = child.next; + } + } +} + +function submitAddition(vNode: VNode): void { + let parent = vNode.parent; + let parentDom; + let tag; + while (parent !== null) { + tag = parent.tag; + if (tag === DomComponent || tag === TreeRoot || tag === DomPortal) { + parentDom = parent.realNode; + break; + } + parent = parent.parent; + } + + if ((parent.flags & ResetText) === ResetText) { + // 在insert之前先reset + clearText(parentDom); + FlagUtils.removeFlag(parent, ResetText); + } + + if ((vNode.flags & DirectAddition) === DirectAddition) { + insertOrAppendPlacementNode(vNode, null, parentDom); + FlagUtils.removeFlag(vNode, DirectAddition); + return; + } + const before = getSiblingDom(vNode); + insertOrAppendPlacementNode(vNode, before, parentDom); +} + function submitClear(vNode: VNode): void { const realNode = vNode.realNode; const cloneDom = realNode.cloneNode(false); // 复制节点后horizon添加给dom的属性未能复制 @@ -397,6 +397,13 @@ function submitDeletion(vNode: VNode): void { clearVNode(vNode); } +function submitSuspenseComponent(vNode: VNode) { + const { childStatus } = vNode.suspenseState; + if (childStatus !== SuspenseChildStatus.Init) { + hideOrUnhideAllChildren(vNode.child, childStatus === SuspenseChildStatus.ShowFallback); + } +} + function submitUpdate(vNode: VNode): void { switch (vNode.tag) { case FunctionComponent: @@ -422,13 +429,6 @@ function submitUpdate(vNode: VNode): void { } } -function submitSuspenseComponent(vNode: VNode) { - const { childStatus } = vNode.suspenseState; - if (childStatus !== SuspenseChildStatus.Init) { - hideOrUnhideAllChildren(vNode.child, childStatus === SuspenseChildStatus.ShowFallback); - } -} - function submitResetTextContent(vNode: VNode) { clearText(vNode.realNode); } From e7c29a632a900a1c40eda85e5654e3a9c490f3f7 Mon Sep 17 00:00:00 2001 From: * <*> Date: Thu, 20 Apr 2023 18:47:51 +0800 Subject: [PATCH 5/7] Match-id-01940cf2f09900b8f170152db9efd6b4de21f6a6 --- .cloudbuild/build.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.cloudbuild/build.yml b/.cloudbuild/build.yml index 905877bf..3d8cd444 100644 --- a/.cloudbuild/build.yml +++ b/.cloudbuild/build.yml @@ -40,3 +40,8 @@ steps: auto: true buildcheck: true mode: sync + POST_BUILD: + - compile_report: + rules: + - warning /.**/ + - error /.**/ From 3d42a535d662ee7475d2716706b63bb1b713873c Mon Sep 17 00:00:00 2001 From: * <*> Date: Sat, 6 May 2023 16:14:22 +0800 Subject: [PATCH 6/7] Match-id-ad7c692603a33470196a2c41cdb3ff21136732d9 --- .../DOMPropertiesHandler.ts | 4 +- libs/horizon/src/event/EventBinding.ts | 3 +- libs/horizon/src/external/ChildrenUtil.ts | 3 +- libs/horizon/src/external/JSXElement.ts | 36 +++++--------- libs/horizon/src/renderer/RootStack.ts | 13 +++-- libs/horizon/src/renderer/Types.ts | 4 +- .../src/renderer/diff/nodeDiffComparator.ts | 9 ++-- .../src/renderer/submit/LifeCycleHandler.ts | 5 +- libs/horizon/src/renderer/vnode/VNode.ts | 4 +- libs/horizon/src/renderer/vnode/VNodeUtils.ts | 4 +- .../ComponentTest/JsxElement.test.js | 48 +++++++++++++++++++ 11 files changed, 90 insertions(+), 43 deletions(-) create mode 100644 scripts/__tests__/ComponentTest/JsxElement.test.js diff --git a/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts b/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts index a0ab84b3..5c0773c1 100644 --- a/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts +++ b/libs/horizon/src/dom/DOMPropertiesHandler/DOMPropertiesHandler.ts @@ -48,7 +48,7 @@ export function setDomProps(dom: Element, props: Object, isNativeTag: boolean, i } } else if (propName === 'dangerouslySetInnerHTML') { dom.innerHTML = propVal.__html; - } else if (!isInit || (isInit && propVal != null)) { + } else if (!isInit || propVal != null) { updateCommonProp(dom, propName, propVal, isNativeTag); } } @@ -70,7 +70,7 @@ export function compareProps(oldProps: Object, newProps: Object): Object { for (let i = 0; i < oldPropsLength; i++) { propName = keysOfOldProps[i]; // 新属性中包含该属性或者该属性为空值的属性不需要处理 - if (keysOfNewProps.includes(propName) || oldProps[propName] == null) { + if ( oldProps[propName] == null || keysOfNewProps.includes(propName)) { continue; } diff --git a/libs/horizon/src/event/EventBinding.ts b/libs/horizon/src/event/EventBinding.ts index 25c52b90..fd7a4d0c 100644 --- a/libs/horizon/src/event/EventBinding.ts +++ b/libs/horizon/src/event/EventBinding.ts @@ -82,8 +82,7 @@ export function lazyDelegateOnRoot(currentRoot: VNode, eventName: string) { } if (!events[nativeFullName]) { - const listener = listenToNativeEvent(nativeEvent, currentRoot.realNode, isCapture); - events[nativeFullName] = listener; + events[nativeFullName] = listenToNativeEvent(nativeEvent, currentRoot.realNode, isCapture); } }); } diff --git a/libs/horizon/src/external/ChildrenUtil.ts b/libs/horizon/src/external/ChildrenUtil.ts index 39c2e96c..3624a736 100644 --- a/libs/horizon/src/external/ChildrenUtil.ts +++ b/libs/horizon/src/external/ChildrenUtil.ts @@ -17,6 +17,7 @@ import { throwIfTrue } from '../renderer/utils/throwIfTrue'; import { TYPE_COMMON_ELEMENT, TYPE_PORTAL } from './JSXElementType'; import { isValidElement, JSXElement } from './JSXElement'; +import { BELONG_CLASS_VNODE_KEY } from '../renderer/vnode/VNode'; // 生成key function getItemKey(item: any, index: number): string { @@ -83,7 +84,7 @@ function callMapFun(children: any, arr: Array, prefix: string, callback: Fu mappedChild.type, newKey, mappedChild.ref, - mappedChild.belongClassVNode, + mappedChild[BELONG_CLASS_VNODE_KEY], mappedChild.props, mappedChild.src ); diff --git a/libs/horizon/src/external/JSXElement.ts b/libs/horizon/src/external/JSXElement.ts index 3de212b3..ccf43079 100644 --- a/libs/horizon/src/external/JSXElement.ts +++ b/libs/horizon/src/external/JSXElement.ts @@ -16,6 +16,7 @@ import { TYPE_COMMON_ELEMENT } from './JSXElementType'; import { getProcessingClassVNode } from '../renderer/GlobalVar'; import { Source } from '../renderer/Types'; +import { BELONG_CLASS_VNODE_KEY } from '../renderer/vnode/VNode'; /** * vtype 节点的类型,这里固定是element @@ -36,17 +37,9 @@ export function JSXElement(type, key, ref, vNode, props, source: Source | null) ref: ref, props: props, - // 所属的class组件 - belongClassVNode: null, + // 所属的class组件,clonedeep jsxElement时需要防止无限循环 + [BELONG_CLASS_VNODE_KEY]: vNode, }; - - // 在 cloneDeep JSXElement 的时候会出现死循环,需要设置belongClassVNode的enumerable为false - Object.defineProperty(ele, 'belongClassVNode', { - configurable: false, - enumerable: false, - value: vNode, - }); - if (isDev) { // 为了test判断两个 JSXElement 对象是否相等时忽略src属性,需要设置src的enumerable为false Object.defineProperty(ele, 'src', { @@ -60,11 +53,6 @@ export function JSXElement(type, key, ref, vNode, props, source: Source | null) return ele; } -function isValidKey(key) { - const keyArray = ['key', 'ref', '__source', '__self']; - return !keyArray.includes(key); -} - function mergeDefault(sourceObj, defaultObj) { Object.keys(defaultObj).forEach(key => { if (sourceObj[key] === undefined) { @@ -73,19 +61,20 @@ function mergeDefault(sourceObj, defaultObj) { }); } +// ['key', 'ref', '__source', '__self']属性不从setting获取 +const keyArray = ['key', 'ref', '__source', '__self']; + function buildElement(isClone, type, setting, children) { // setting中的值优先级最高,clone情况下从 type 中取值,创建情况下直接赋值为 null - const key = setting && setting.key !== undefined ? String(setting.key) : isClone ? type.key : null; - const ref = setting && setting.ref !== undefined ? setting.ref : isClone ? type.ref : null; + const key = (setting && setting.key !== undefined) ? String(setting.key) : (isClone ? type.key : null); + const ref = (setting && setting.ref !== undefined) ? setting.ref : (isClone ? type.ref : null); const props = isClone ? { ...type.props } : {}; - let vNode = isClone ? type.belongClassVNode : getProcessingClassVNode(); + let vNode = isClone ? type[BELONG_CLASS_VNODE_KEY] : getProcessingClassVNode(); if (setting !== null && setting !== undefined) { - const keys = Object.keys(setting); - const keyLength = keys.length; - for (let i = 0; i < keyLength; i++) { - const k = keys[i]; - if (isValidKey(k)) { + + for (const k in setting) { + if (!keyArray.includes(k)) { props[k] = setting[k]; } } @@ -109,7 +98,6 @@ function buildElement(isClone, type, setting, children) { lineNumber: setting.__source.lineNumber, }; } - return JSXElement(element, key, ref, vNode, props, src); } diff --git a/libs/horizon/src/renderer/RootStack.ts b/libs/horizon/src/renderer/RootStack.ts index 6a793a0f..814ca840 100644 --- a/libs/horizon/src/renderer/RootStack.ts +++ b/libs/horizon/src/renderer/RootStack.ts @@ -15,15 +15,20 @@ import { VNode } from './vnode/VNode'; -const currentRootStack: VNode[] = []; +const currentRootStack: (VNode | undefined)[] = []; +let index = -1; export function getCurrentRoot() { - return currentRootStack[currentRootStack.length - 1]; + return currentRootStack[index]; } export function pushCurrentRoot(root: VNode) { - return currentRootStack.push(root); + index++; + currentRootStack[index] = root; } export function popCurrentRoot() { - return currentRootStack.pop(); + const target = currentRootStack[index]; + currentRootStack[index] = undefined; + index--; + return target; } diff --git a/libs/horizon/src/renderer/Types.ts b/libs/horizon/src/renderer/Types.ts index 91b6bfbd..9598855e 100644 --- a/libs/horizon/src/renderer/Types.ts +++ b/libs/horizon/src/renderer/Types.ts @@ -13,6 +13,8 @@ * See the Mulan PSL v2 for more details. */ +import { BELONG_CLASS_VNODE_KEY } from './vnode/VNode'; + export { VNode } from './vnode/VNode'; type Trigger = (A) => void; @@ -32,7 +34,7 @@ export type JSXElement = { key: any; ref: any; props: any; - belongClassVNode: any; + [BELONG_CLASS_VNODE_KEY]: any; }; export type ProviderType = { diff --git a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts index 61706bea..42f30b53 100644 --- a/libs/horizon/src/renderer/diff/nodeDiffComparator.ts +++ b/libs/horizon/src/renderer/diff/nodeDiffComparator.ts @@ -27,6 +27,7 @@ import { import { isSameType, getIteratorFn, isTextType, isIteratorType, isObjectType } from './DiffTools'; import { travelChildren } from '../vnode/VNodeUtils'; import { markVNodePath } from '../utils/vNodePath'; +import { BELONG_CLASS_VNODE_KEY } from '../vnode/VNode'; enum DiffCategory { TEXT_NODE = 'TEXT_NODE', @@ -166,11 +167,11 @@ function getNewNode(parentNode: VNode, newChild: any, oldNode: VNode | null) { if (oldNode === null || !isSameType(oldNode, newChild)) { resultNode = createVNodeFromElement(newChild); resultNode.ref = newChild.ref; - resultNode.belongClassVNode = newChild.belongClassVNode; + resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY]; } else { resultNode = updateVNode(oldNode, newChild.props); resultNode.ref = newChild.ref; - resultNode.belongClassVNode = newChild.belongClassVNode; + resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY]; } break; } else if (newChild.vtype === TYPE_PORTAL) { @@ -570,7 +571,7 @@ function diffObjectNodeHandler( } else if (isSameType(canReuseNode, newChild)) { resultNode = updateVNode(canReuseNode, newChild.props); resultNode.ref = newChild.ref; - resultNode.belongClassVNode = newChild.belongClassVNode; + resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY]; startDelVNode = resultNode.next; resultNode.next = null; } @@ -583,7 +584,7 @@ function diffObjectNodeHandler( } else { resultNode = createVNodeFromElement(newChild); resultNode.ref = newChild.ref; - resultNode.belongClassVNode = newChild.belongClassVNode; + resultNode[BELONG_CLASS_VNODE_KEY] = newChild[BELONG_CLASS_VNODE_KEY]; } } } else if (newChild.vtype === TYPE_PORTAL) { diff --git a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts index 056f18f9..5d02368e 100644 --- a/libs/horizon/src/renderer/submit/LifeCycleHandler.ts +++ b/libs/horizon/src/renderer/submit/LifeCycleHandler.ts @@ -52,6 +52,7 @@ import { import { handleSubmitError } from '../ErrorHandler'; import { travelVNodeTree, clearVNode, isDomVNode, getSiblingDom } from '../vnode/VNodeUtils'; import { shouldAutoFocus } from '../../dom/utils/Common'; +import { BELONG_CLASS_VNODE_KEY } from '../vnode/VNode'; function callComponentWillUnmount(vNode: VNode, instance: any) { try { @@ -163,8 +164,8 @@ function handleRef(vNode: VNode, ref, val) { } else if (refType === 'object') { (ref).current = val; } else { - if (vNode.belongClassVNode && vNode.belongClassVNode.realNode) { - vNode.belongClassVNode.realNode.refs[String(ref)] = val; + if (vNode[BELONG_CLASS_VNODE_KEY] && vNode[BELONG_CLASS_VNODE_KEY].realNode) { + vNode[BELONG_CLASS_VNODE_KEY].realNode.refs[String(ref)] = val; } } } diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 40ba0554..4911f887 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -38,6 +38,8 @@ import type { Hook } from '../hooks/HookType'; import { InitFlag } from './VNodeFlags'; import { Observer } from '../../horizonx/proxy/Observer'; +export const BELONG_CLASS_VNODE_KEY = Symbol('belongClassVNode'); + export class VNode { tag: VNodeTag; key: string | null; // 唯一标识符 @@ -97,7 +99,7 @@ export class VNode { toUpdateNodes: Set | null; // 保存要更新的节点 delegatedEvents: Set; - belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用 + [BELONG_CLASS_VNODE_KEY]: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用 // 状态管理器HorizonX使用 isStoreChange: boolean; diff --git a/libs/horizon/src/renderer/vnode/VNodeUtils.ts b/libs/horizon/src/renderer/vnode/VNodeUtils.ts index d9bb0f50..b195f886 100644 --- a/libs/horizon/src/renderer/vnode/VNodeUtils.ts +++ b/libs/horizon/src/renderer/vnode/VNodeUtils.ts @@ -20,9 +20,9 @@ import type {VNode} from '../Types'; import {DomComponent, DomPortal, DomText, TreeRoot} from './VNodeTags'; -import {isComment} from '../../dom/utils/Common'; import {getNearestVNode} from '../../dom/DOMInternalKeys'; import {Addition, InitFlag} from './VNodeFlags'; +import { BELONG_CLASS_VNODE_KEY } from './VNode'; export function travelChildren( beginVNode: VNode | null, @@ -124,7 +124,7 @@ export function clearVNode(vNode: VNode) { vNode.toUpdateNodes = null; - vNode.belongClassVNode = null; + vNode[BELONG_CLASS_VNODE_KEY] = null; if (window.__HORIZON_DEV_HOOK__) { const hook = window.__HORIZON_DEV_HOOK__; hook.deleteVNode(vNode); diff --git a/scripts/__tests__/ComponentTest/JsxElement.test.js b/scripts/__tests__/ComponentTest/JsxElement.test.js new file mode 100644 index 00000000..8794fbbf --- /dev/null +++ b/scripts/__tests__/ComponentTest/JsxElement.test.js @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * + * openGauss is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +import * as Horizon from '@cloudsop/horizon/index.ts'; + +describe('JSX Element test', () => { + it('symbol attribute prevent cloneDeep unlimited loop', function () { + + function cloneDeep(obj) { + const result = {}; + Object.keys(obj).forEach(key => { + if (obj[key] && typeof obj[key] === 'object') { + result[key] = cloneDeep(obj[key]); + } else { + result[key] = obj[key]; + } + }) + return result; + } + class Demo extends Horizon.Component { + render() { + return ( +
+ hello +
+ ); + } + } + + const ele = Horizon.createElement(Demo); + const copy = cloneDeep(ele); + expect(copy.vtype).toEqual(ele.vtype); + expect(Object.getOwnPropertySymbols(copy).length).toEqual(0); + }); +}); + From c2b21d887fab5d012e2c9aa1e9217cda3d453bb6 Mon Sep 17 00:00:00 2001 From: * <*> Date: Wed, 10 May 2023 10:36:46 +0800 Subject: [PATCH 7/7] Match-id-d5ebb7ce3bcb7378a88b90977da5b6329e0a5990 --- CHANGELOG.md | 4 ++++ libs/horizon/package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f42fe512..d019e752 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.0.45 (2023-05-10) +- **core**: 修改belongClassVNode属性为Symbol提升性能 +- **core**: 优化内部循环实现,提升性能 + ## 0.0.44 (2023-04-03) - **core**: 修复horizonX-devtool Firefox75报错 diff --git a/libs/horizon/package.json b/libs/horizon/package.json index 6eb58684..5cf8027c 100644 --- a/libs/horizon/package.json +++ b/libs/horizon/package.json @@ -4,7 +4,7 @@ "keywords": [ "horizon" ], - "version": "0.0.44", + "version": "0.0.45", "homepage": "", "bugs": "", "main": "index.js",