diff --git a/libs/horizon/index.ts b/libs/horizon/index.ts index 64762f9e..cc37e0d0 100644 --- a/libs/horizon/index.ts +++ b/libs/horizon/index.ts @@ -23,6 +23,9 @@ import { createElement, cloneElement, isValidElement, + act, + launchUpdateFromVNode as _launchUpdateFromVNode, + getProcessingVNode as _getProcessingVNode, } from './src/external/Horizon'; import { @@ -63,6 +66,9 @@ const Horizon = { unstable_batchedUpdates, findDOMNode, unmountComponentAtNode, + act, + _launchUpdateFromVNode, + _getProcessingVNode, }; export { @@ -95,6 +101,9 @@ export { unstable_batchedUpdates, findDOMNode, unmountComponentAtNode, + act, + _launchUpdateFromVNode, + _getProcessingVNode, }; export default Horizon; diff --git a/libs/horizon/src/external/Horizon.ts b/libs/horizon/src/external/Horizon.ts index 623bfbb4..6644f2a0 100644 --- a/libs/horizon/src/external/Horizon.ts +++ b/libs/horizon/src/external/Horizon.ts @@ -17,7 +17,6 @@ import {createContext} from '../renderer/components/context/CreateContext'; import {lazy} from '../renderer/components/Lazy'; import {forwardRef} from '../renderer/components/ForwardRef'; import {memo} from '../renderer/components/Memo'; -import hookMapping from '../renderer/hooks/HookMapping'; import { useCallback, @@ -30,6 +29,16 @@ import { useRef, useState, } from '../renderer/hooks/HookExternal'; +import {launchUpdateFromVNode, asyncUpdates} from '../renderer/TreeBuilder'; +import {callRenderQueueImmediate} from '../renderer/taskExecutor/RenderQueue'; +import {runAsyncEffects} from '../renderer/submit/HookEffectHandler'; +import { getProcessingVNode } from '../renderer/hooks/BaseHook'; + +const act = (fun) => { + asyncUpdates(fun); + callRenderQueueImmediate(); + runAsyncEffects(); +} export { Children, @@ -56,5 +65,7 @@ export { createElement, cloneElement, isValidElement, - hookMapping, + act, + launchUpdateFromVNode, + getProcessingVNode, }; diff --git a/libs/horizon/src/renderer/hooks/HookExternal.ts b/libs/horizon/src/renderer/hooks/HookExternal.ts index fc2805ab..e4610f43 100644 --- a/libs/horizon/src/renderer/hooks/HookExternal.ts +++ b/libs/horizon/src/renderer/hooks/HookExternal.ts @@ -1,17 +1,15 @@ import type {ContextType} from '../Types'; -import hookMapping from './HookMapping'; import {useRefImpl} from './UseRefHook'; import {useEffectImpl, useLayoutEffectImpl} from './UseEffectHook'; import {useCallbackImpl} from './UseCallbackHook'; import {useMemoImpl} from './UseMemoHook'; import {useImperativeHandleImpl} from './UseImperativeHook'; - -const { - UseContextHookMapping, - UseReducerHookMapping, - UseStateHookMapping -} = hookMapping; +import {useReducerImpl} from './UseReducerHook'; +import {useStateImpl} from './UseStateHook'; +import {getNewContext} from '../components/context/Context'; +import {getProcessingVNode} from './BaseHook'; +import {Ref, Trigger} from './HookType'; type BasicStateAction = ((S) => S) | S; type Dispatch = (A) => void; @@ -20,22 +18,23 @@ type Dispatch = (A) => void; export function useContext( Context: ContextType, ): T { - return UseContextHookMapping.val.useContext(Context); + const processingVNode = getProcessingVNode(); + return getNewContext(processingVNode!, Context, true); } export function useState(initialState: (() => S) | S,): [S, Dispatch>] { - return UseStateHookMapping.val.useState(initialState); + return useStateImpl(initialState); } export function useReducer( reducer: (S, A) => S, initialArg: I, init?: (I) => S, -): [S, Dispatch] { - return UseReducerHookMapping.val.useReducer(reducer, initialArg, init); +): [S, Trigger] | void { + return useReducerImpl(reducer, initialArg, init); } -export function useRef(initialValue: T): {current: T} { +export function useRef(initialValue: T): Ref { return useRefImpl(initialValue); } diff --git a/libs/horizon/src/renderer/hooks/HookMain.ts b/libs/horizon/src/renderer/hooks/HookMain.ts index f4c568ea..675afdbb 100644 --- a/libs/horizon/src/renderer/hooks/HookMain.ts +++ b/libs/horizon/src/renderer/hooks/HookMain.ts @@ -1,22 +1,11 @@ import type {VNode} from '../Types'; -import hookMapping from './HookMapping'; -const { - UseStateHookMapping, - UseReducerHookMapping, - UseContextHookMapping, -} = hookMapping; - -import {getNewContext} from '../components/context/Context'; import { getLastTimeHook, - getProcessingVNode, setLastTimeHook, setProcessingVNode, setCurrentHook, getNextHook } from './BaseHook'; -import {useStateImpl} from './UseStateHook'; -import {useReducerImpl} from './UseReducerHook'; import {HookStage, setHookStage} from './HookStage'; // hook对外入口 @@ -29,9 +18,6 @@ export function exeFunctionHook, Arg>( // 重置全局变量 resetGlobalVariable(); - // 初始化hook实现函数 - initHookMapping(); - setProcessingVNode(processing); processing.oldHooks = processing.hooks; @@ -71,8 +57,3 @@ function resetGlobalVariable() { setCurrentHook(null); } -export function initHookMapping() { - UseContextHookMapping.val = {useContext: context => getNewContext(getProcessingVNode(), context, true)}; - UseReducerHookMapping.val = {useReducer: useReducerImpl}; - UseStateHookMapping.val = {useState: useStateImpl}; -} diff --git a/libs/horizon/src/renderer/hooks/HookMapping.ts b/libs/horizon/src/renderer/hooks/HookMapping.ts deleted file mode 100644 index 2205d029..00000000 --- a/libs/horizon/src/renderer/hooks/HookMapping.ts +++ /dev/null @@ -1,21 +0,0 @@ -/** - * 暂时用于解决测试代码无法运行问题,估计是:测试代码会循环或者重复依赖 - */ - -import type { - UseContextHookType, - UseReducerHookType, - UseStateHookType -} from '../Types'; - -const UseStateHookMapping: {val: (null | UseStateHookType)} = {val: null}; -const UseReducerHookMapping: {val: (null | UseReducerHookType)} = {val: null}; -const UseContextHookMapping: {val: (null | UseContextHookType)} = {val: null}; - -const hookMapping = { - UseStateHookMapping, - UseReducerHookMapping, - UseContextHookMapping, -} - -export default hookMapping; diff --git a/libs/horizon/src/renderer/render/FunctionComponent.ts b/libs/horizon/src/renderer/render/FunctionComponent.ts index 8191293b..d7397e32 100644 --- a/libs/horizon/src/renderer/render/FunctionComponent.ts +++ b/libs/horizon/src/renderer/render/FunctionComponent.ts @@ -76,12 +76,14 @@ export function captureFunctionComponent( ); // 这里需要判断是否可以复用,因为函数组件比起其他组价,多了context和stateChange两个因素 - if (isCanReuse && !isStateChange()) { + if (isCanReuse && !isStateChange() && !processing.isStoreChange) { FlagUtils.removeFlag(processing, Update); return onlyUpdateChildVNodes(processing); } + processing.isStoreChange = false; + processing.child = createChildrenByDiff(processing, processing.child, newElements, !processing.isCreated); return processing.child; } diff --git a/libs/horizon/src/renderer/vnode/VNode.ts b/libs/horizon/src/renderer/vnode/VNode.ts index 8c98e673..1bd90f75 100644 --- a/libs/horizon/src/renderer/vnode/VNode.ts +++ b/libs/horizon/src/renderer/vnode/VNode.ts @@ -66,6 +66,10 @@ export class VNode { belongClassVNode: VNode | null = null; // 记录JSXElement所属class vNode,处理ref的时候使用 + // 状态管理器使用 + isStoreChange: boolean; + functionToObserver: FunctionToObserver | null; // 记录这个函数组件依赖哪些Observer + constructor(tag: VNodeTag, props: any, key: null | string, realNode) { this.tag = tag; // 对应组件的类型,比如ClassComponent等 this.key = key; @@ -90,6 +94,8 @@ export class VNode { this.depContexts = null; this.isDepContextChange = false; this.oldHooks = null; + this.isStoreChange = false; + this.functionToObserver = null; break; case ClassComponent: this.realNode = null; diff --git a/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js index d94443b7..7cb53f24 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseContext.test.js @@ -1,9 +1,7 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; -import { act } from '../../jest/customMatcher'; describe('useContext Hook Test', () => { - const { useState, useContext } = Horizon; - const { unmountComponentAtNode } = Horizon; + const { useState, useContext, act, unmountComponentAtNode } = Horizon; it('简单使用useContext', () => { const LanguageTypes = { diff --git a/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js b/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js index b1bc34fe..f5b66614 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseEffect.test.js @@ -1,7 +1,6 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import { act } from '../../jest/customMatcher'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useEffect Hook Test', () => { const { @@ -9,7 +8,8 @@ describe('useEffect Hook Test', () => { useLayoutEffect, useState, memo, - forwardRef + forwardRef, + act, } = Horizon; it('简单使用useEffect', () => { diff --git a/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js b/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js index b7f5d6e8..99a7af6a 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseImperativeHandle.test.js @@ -1,13 +1,13 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import { act } from '../../jest/customMatcher'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useImperativeHandle Hook Test', () => { const { useState, useImperativeHandle, - forwardRef + forwardRef, + act, } = Horizon; const { unmountComponentAtNode } = Horizon; diff --git a/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js b/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js index 84226eac..759f6cde 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseLayoutEffect.test.js @@ -1,13 +1,13 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import { act } from '../../jest/customMatcher'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useLayoutEffect Hook Test', () => { const { useState, useEffect, - useLayoutEffect + useLayoutEffect, + act, } = Horizon; it('简单使用useLayoutEffect', () => { diff --git a/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js b/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js index 41882449..0e144f70 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseMemo.test.js @@ -1,6 +1,6 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useMemo Hook Test', () => { const { useMemo, useState } = Horizon; diff --git a/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js b/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js index b2bab667..a9defa95 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseRef.test.js @@ -1,6 +1,6 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useRef Hook Test', () => { const { useState, useRef } = Horizon; diff --git a/scripts/__tests__/ComponentTest/HookTest/UseState.test.js b/scripts/__tests__/ComponentTest/HookTest/UseState.test.js index b9e1d950..7e485671 100644 --- a/scripts/__tests__/ComponentTest/HookTest/UseState.test.js +++ b/scripts/__tests__/ComponentTest/HookTest/UseState.test.js @@ -1,14 +1,14 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../../jest/logUtils'; -import { act } from '../../jest/customMatcher'; -import Text from '../../jest/Text'; +import { Text } from '../../jest/commonComponents'; describe('useState Hook Test', () => { const { useState, forwardRef, useImperativeHandle, - memo + memo, + act, } = Horizon; it('简单使用useState', () => { diff --git a/scripts/__tests__/EventTest/FocusEvent.test.js b/scripts/__tests__/EventTest/FocusEvent.test.js index ee5e3c46..d796960e 100644 --- a/scripts/__tests__/EventTest/FocusEvent.test.js +++ b/scripts/__tests__/EventTest/FocusEvent.test.js @@ -1,6 +1,5 @@ import * as Horizon from '@cloudsop/horizon/index.ts'; import * as LogUtils from '../jest/logUtils'; -import { act } from '../jest/customMatcher'; describe('合成焦点事件', () => { @@ -43,4 +42,4 @@ describe('合成焦点事件', () => { 'onBlur: blur', ]); }) -}) \ No newline at end of file +}) diff --git a/scripts/__tests__/jest/Text.js b/scripts/__tests__/jest/Text.js deleted file mode 100644 index 781c6d5a..00000000 --- a/scripts/__tests__/jest/Text.js +++ /dev/null @@ -1,9 +0,0 @@ -import * as Horizon from '@cloudsop/horizon/index.ts'; -import * as LogUtils from '../jest/logUtils'; - -const Text = (props) => { - LogUtils.log(props.text); - return

{props.text}

; -}; - -export default Text; diff --git a/scripts/__tests__/jest/commonComponents.js b/scripts/__tests__/jest/commonComponents.js new file mode 100644 index 00000000..138e18e7 --- /dev/null +++ b/scripts/__tests__/jest/commonComponents.js @@ -0,0 +1,20 @@ +import * as Horizon from '@cloudsop/horizon/index.ts'; +import * as LogUtils from './logUtils'; + +export const App = (props) => { + const Parent = props.parent; + const Child = props.child; + + return ( +
+ + + +
+ ); +} + +export const Text = (props) => { + LogUtils.log(props.text); + return

{props.text}

; +} diff --git a/scripts/__tests__/jest/customMatcher.js b/scripts/__tests__/jest/customMatcher.js deleted file mode 100644 index 1f6a96fb..00000000 --- a/scripts/__tests__/jest/customMatcher.js +++ /dev/null @@ -1,33 +0,0 @@ -import { runAsyncEffects } from '../../../libs/horizon/src/renderer/submit/HookEffectHandler'; -import { callRenderQueueImmediate } from '../../../libs/horizon/src/renderer/taskExecutor/RenderQueue'; -import { asyncUpdates } from '../../../libs/horizon/src/renderer/TreeBuilder'; - -function runAssertion(fn) { - try { - fn(); - } catch (error) { - return { - pass: false, - message: () => error.message, - }; - } - return { pass: true }; -} - -function toMatchValue(LogUtils, expectedValues) { - return runAssertion(() => { - const actualValues = LogUtils.getAndClear(); - expect(actualValues).toEqual(expectedValues); - }); -} - -const act = (fun) => { - asyncUpdates(fun); - callRenderQueueImmediate(); - runAsyncEffects(); -} - -module.exports = { - toMatchValue, - act -}; diff --git a/scripts/__tests__/jest/jestSetting.js b/scripts/__tests__/jest/jestSetting.js index deda8dd9..78773f65 100644 --- a/scripts/__tests__/jest/jestSetting.js +++ b/scripts/__tests__/jest/jestSetting.js @@ -17,7 +17,27 @@ global.afterEach(() => { LogUtils.clear(); }); + +function runAssertion(fn) { + try { + fn(); + } catch (error) { + return { + pass: false, + message: () => error.message, + }; + } + return { pass: true }; +} + +function toMatchValue(LogUtils, expectedValues) { + return runAssertion(() => { + const actualValues = LogUtils.getAndClear(); + expect(actualValues).toEqual(expectedValues); + }); +} + // 使Jest感知自定义匹配器 expect.extend({ - ...require('./customMatcher'), + toMatchValue, }); diff --git a/scripts/__tests__/jest/testUtils.js b/scripts/__tests__/jest/testUtils.js index 8bab93d9..4afda9da 100644 --- a/scripts/__tests__/jest/testUtils.js +++ b/scripts/__tests__/jest/testUtils.js @@ -9,7 +9,7 @@ export const stopBubbleOrCapture = (e, value) => { export const getEventListeners = (dom) => { let ret = true let keyArray = []; - for (var key in dom) { + for (let key in dom) { keyArray.push(key); } try { @@ -23,4 +23,11 @@ export const getEventListeners = (dom) => { } return ret; -}; \ No newline at end of file +}; + +export function triggerClickEvent(container, id) { + const event = new MouseEvent('click', { + bubbles: true, + }); + container.querySelector(`#${id}`).dispatchEvent(event); +}