From 3fb66526071fec18098ac2ac246fddf4e6adf80c Mon Sep 17 00:00:00 2001 From: huangxuan Date: Thu, 30 Nov 2023 16:09:33 +0800 Subject: [PATCH 1/8] =?UTF-8?q?[inulax]=20=E4=BF=AE=E5=A4=8D=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E4=BD=BF=E7=94=A8connect=20API=EF=BC=8Cprops=E9=87=8C?= =?UTF-8?q?=E6=B2=A1=E6=9C=89dispatch=E6=96=B9=E6=B3=95=EF=BC=9B=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E5=AF=B9=E8=B1=A1=E6=AF=94=E8=BE=83=E7=AE=97=E6=B3=95?= =?UTF-8?q?=EF=BC=8C=E9=98=B2=E6=AD=A2=E6=AD=BB=E5=BE=AA=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/inula/src/inulax/CommonUtils.ts | 54 +++++++++++++++---- .../inula/src/inulax/adapters/reduxReact.ts | 1 + 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/packages/inula/src/inulax/CommonUtils.ts b/packages/inula/src/inulax/CommonUtils.ts index 5202a200..ccbb182f 100644 --- a/packages/inula/src/inulax/CommonUtils.ts +++ b/packages/inula/src/inulax/CommonUtils.ts @@ -67,18 +67,50 @@ export function isPromise(obj: any): boolean { return isObject(obj) && typeof obj.then === 'function'; } -export function isSame(x, y) { - if (typeof Object.is !== 'function') { - if (x === y) { - // +0 != -0 - return x !== 0 || 1 / x === 1 / y; - } else { - // NaN == NaN - return x !== x && y !== y; - } - } else { - return Object.is(x, y); +export function isSame(x: unknown, y: unknown): boolean { + // 如果两个对象是同一个引用,直接返回true + if (x === y) { + return true; } + // 如果两个对象类型不同,直接返回false + if (typeof x !== typeof y) { + return false; + } + // 如果两个对象都是null或undefined,直接返回true + if (x == null || y == null) { + return true; + } + // 如果两个对象都是基本类型,比较他们的值是否相等 + if (typeof x !== 'object') { + return x === y; + } + // 如果两个对象都是数组,比较他们的长度是否相等,然后递归比较每个元素是否相等 + if (Array.isArray(x) && Array.isArray(y)) { + if (x.length !== y.length) { + return false; + } + for (let i = 0; i < x.length; i++) { + if (!isSame(x[i], y[i])) { + return false; + } + } + return true; + } + // 两个对象都是普通对象,首先比较他们的属性数量是否相等,然后递归比较每个属性的值是否相等 + if (typeof x === 'object' && typeof y === 'object') { + const keys1 = Object.keys(x!).sort(); + const keys2 = Object.keys(y!).sort(); + if (keys1.length !== keys2.length) { + return false; + } + for (let i = 0; i < keys1.length; i++) { + if (!isSame(x![keys1[i]], y![keys2[i]])) { + return false; + } + } + return true; + } + return false; } export function getDetailedType(val: any) { diff --git a/packages/inula/src/inulax/adapters/reduxReact.ts b/packages/inula/src/inulax/adapters/reduxReact.ts index 160dec37..1cc6cbb8 100644 --- a/packages/inula/src/inulax/adapters/reduxReact.ts +++ b/packages/inula/src/inulax/adapters/reduxReact.ts @@ -162,6 +162,7 @@ export function connect( mappedDispatch = mapDispatchToProps(store.dispatch, props); } } + mappedDispatch = Object.assign({}, mappedDispatch, { dispatch: store.dispatch }); const mergedProps = ( mergeProps || ((state, dispatch, originalProps) => { From 3019fa55811277a0d364408b2a24826cb1dd0d22 Mon Sep 17 00:00:00 2001 From: huangxuan Date: Thu, 30 Nov 2023 16:11:37 +0800 Subject: [PATCH 2/8] =?UTF-8?q?[inulax]=20=E4=BF=AE=E5=A4=8Dmiddleware(...?= =?UTF-8?q?)=20is=20not=20a=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/inula/src/inulax/adapters/redux.ts | 45 +++++++++++-------- .../inula/src/inulax/adapters/reduxThunk.ts | 4 +- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/packages/inula/src/inulax/adapters/redux.ts b/packages/inula/src/inulax/adapters/redux.ts index b996abed..dbcb6924 100644 --- a/packages/inula/src/inulax/adapters/redux.ts +++ b/packages/inula/src/inulax/adapters/redux.ts @@ -27,12 +27,12 @@ export { createDispatchHook, } from './reduxReact'; -export type ReduxStoreHandler = { - reducer: (state: any, action: { type: string }) => any; - dispatch: (action: { type: string }) => void; - getState: () => any; - subscribe: (listener: () => void) => () => void; - replaceReducer: (reducer: (state: any, action: { type: string }) => any) => void; +export type ReduxStoreHandler = { + reducer(state: T, action: { type: string }): any; + dispatch(action: { type: string }): void; + getState(): T; + subscribe(listener: () => void): () => void; + replaceReducer(reducer: (state: T, action: { type: string }) => any): void; }; export type ReduxAction = { @@ -53,6 +53,9 @@ export type ReduxMiddleware = ( type Reducer = (state: any, action: ReduxAction) => any; +type StoreCreator = (reducer: Reducer, preloadedState?: any) => ReduxStoreHandler +type StoreEnhancer = (next: StoreCreator) => StoreCreator + function mergeData(state, data) { if (!data) { state.stateWrapper = data; @@ -87,7 +90,7 @@ function mergeData(state, data) { state.stateWrapper = data; } -export function createStore(reducer: Reducer, preloadedState?: any, enhancers?): ReduxStoreHandler { +export function createStore(reducer: Reducer, preloadedState?: any, enhancers?: StoreEnhancer): ReduxStoreHandler { const store = createStoreX({ id: 'defaultStore', state: { stateWrapper: preloadedState }, @@ -150,19 +153,23 @@ export function combineReducers(reducers: { [key: string]: Reducer }): Reducer { }; } -function applyMiddlewares(store: ReduxStoreHandler, middlewares: ReduxMiddleware[]): void { - middlewares = middlewares.slice(); - middlewares.reverse(); - let dispatch = store.dispatch; - middlewares.forEach(middleware => { - dispatch = middleware(store)(dispatch); - }); - store.dispatch = dispatch; +function applyMiddlewares(createStore: StoreCreator, middlewares: ReduxMiddleware[]): StoreCreator { + return (reducer, preloadedState) => { + middlewares = middlewares.slice(); + middlewares.reverse(); + const storeObj = createStore(reducer, preloadedState); + let dispatch = storeObj.dispatch; + middlewares.forEach(middleware => { + dispatch = middleware(storeObj)(dispatch); + }); + storeObj.dispatch = dispatch; + return storeObj; + }; } -export function applyMiddleware(...middlewares: ReduxMiddleware[]): (store: ReduxStoreHandler) => void { - return store => { - return applyMiddlewares(store, middlewares); +export function applyMiddleware(...middlewares: ReduxMiddleware[]): (createStore: StoreCreator) => StoreCreator { + return createStore => { + return applyMiddlewares(createStore, middlewares); }; } @@ -170,7 +177,7 @@ type ActionCreator = (...params: any[]) => ReduxAction; type ActionCreators = { [key: string]: ActionCreator }; export type BoundActionCreator = (...params: any[]) => void; type BoundActionCreators = { [key: string]: BoundActionCreator }; -type Dispatch = (action) => any; +type Dispatch = (action: ReduxAction) => any; export function bindActionCreators(actionCreators: ActionCreators, dispatch: Dispatch): BoundActionCreators { const boundActionCreators = {}; diff --git a/packages/inula/src/inulax/adapters/reduxThunk.ts b/packages/inula/src/inulax/adapters/reduxThunk.ts index cd14fee6..7eaaf227 100644 --- a/packages/inula/src/inulax/adapters/reduxThunk.ts +++ b/packages/inula/src/inulax/adapters/reduxThunk.ts @@ -35,5 +35,5 @@ function createThunkMiddleware(extraArgument?: any): ReduxMiddleware { } export const thunk = createThunkMiddleware(); -// @ts-ignore -thunk.withExtraArgument = createThunkMiddleware; + +export const withExtraArgument = createThunkMiddleware; From 0f87229318dc2439e0eb054f6c6c5fe52c217b93 Mon Sep 17 00:00:00 2001 From: huangxuan Date: Thu, 30 Nov 2023 16:13:51 +0800 Subject: [PATCH 3/8] =?UTF-8?q?[inulax]=20=E7=8A=B6=E6=80=81=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=99=A8dispatch=E6=96=B9=E6=B3=95=E5=BC=82=E6=AD=A5?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E6=97=B6=E6=95=B0=E6=8D=AE=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inula/src/inulax/adapters/reduxReact.ts | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/inula/src/inulax/adapters/reduxReact.ts b/packages/inula/src/inulax/adapters/reduxReact.ts index 1cc6cbb8..4f3bd675 100644 --- a/packages/inula/src/inulax/adapters/reduxReact.ts +++ b/packages/inula/src/inulax/adapters/reduxReact.ts @@ -41,29 +41,27 @@ export function createStoreHook(context: Context): () => ReduxStoreHandler { }; } -export function createSelectorHook(context: Context): (selector?: (any) => any) => any { - const store = createStoreHook(context)() as unknown as ReduxStoreHandler; - return function (selector = state => state) { - const [b, fr] = useState(false); +export function createSelectorHook(context: Context): (selector?: ((state: unknown) => any) | undefined) => any { + const store = createStoreHook(context)(); + return function useSelector(selector = state => state) { + const [state, setState] = useState(() => store.getState()); useEffect(() => { - const unsubscribe = store.subscribe(() => fr(!b)); - return () => { - unsubscribe(); - }; - }); + const unsubscribe = store.subscribe(() => { + setState(store.getState()); + }); + return () => unsubscribe(); + }, []); - return selector(store.getState()); + return selector(state); }; } export function createDispatchHook(context: Context): () => BoundActionCreator { - const store = createStoreHook(context)() as unknown as ReduxStoreHandler; - return function () { - return action => { - store.dispatch(action); - }; - }.bind(store); + const store = createStoreHook(context)(); + return function useDispatch() { + return store.dispatch; + }; } export const useSelector = selector => { From e7dfd1b7e56557fc922c1135f056313f91872fd1 Mon Sep 17 00:00:00 2001 From: huangxuan Date: Thu, 30 Nov 2023 16:15:44 +0800 Subject: [PATCH 4/8] =?UTF-8?q?[inulax]=20=E4=BF=AE=E5=A4=8D=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E7=AE=A1=E7=90=86=E5=99=A8=E6=9B=B4=E6=96=B0=E5=AE=8C?= =?UTF-8?q?=E5=80=BC=E5=90=8E=EF=BC=8Cthis.props=E9=87=8C=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E4=B8=8D=E5=88=B0=E6=9C=80=E6=96=B0=E7=BB=93=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../inula/src/inulax/adapters/reduxReact.ts | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/packages/inula/src/inulax/adapters/reduxReact.ts b/packages/inula/src/inulax/adapters/reduxReact.ts index 4f3bd675..c404976f 100644 --- a/packages/inula/src/inulax/adapters/reduxReact.ts +++ b/packages/inula/src/inulax/adapters/reduxReact.ts @@ -111,41 +111,36 @@ export function connect( //this component should bear the type returned from mapping functions return (Component: OriginalComponent): WrappedComponent => { - const useStore = createStoreHook(options?.context || DefaultContext); + const useStore = createStoreHook(options.context || DefaultContext); //this component should mimic original type of component used const Wrapper: WrappedComponent = (props: OwnProps) => { - const [f, forceReload] = useState(true); + const store = useStore() as ReduxStoreHandler; + const [state, setState] = useState(() => store.getState()); - const store = useStore() as unknown as ReduxStoreHandler; useEffect(() => { - const unsubscribe = store.subscribe(() => forceReload(!f)); - return () => { - unsubscribe(); - }; + const unsubscribe = store.subscribe(() => { + setState(store.getState()); + }); + return () => unsubscribe(); + }, []); + + const previous = useRef<{ state: { [key: string]: any }, mappedState: StateProps }>({ + state: {}, + mappedState: {} as StateProps, }); - const previous = useRef({ - state: {}, - mappedState: {}, - }) as { - current: { - state: { [key: string]: any }; - mappedState: StateProps; - }; - }; - let mappedState: StateProps; - if (options?.areStatesEqual) { - if (options.areStatesEqual(previous.current.state, store.getState())) { + if (options.areStatesEqual) { + if (options.areStatesEqual(previous.current.state, state)) { mappedState = previous.current.mappedState as StateProps; } else { - mappedState = mapStateToProps ? mapStateToProps(store.getState(), props) : ({} as StateProps); + mappedState = mapStateToProps ? mapStateToProps(state, props) : ({} as StateProps); previous.current.mappedState = mappedState; } } else { - mappedState = mapStateToProps ? mapStateToProps(store.getState(), props) : ({} as StateProps); + mappedState = mapStateToProps ? mapStateToProps(state, props) : ({} as StateProps); previous.current.mappedState = mappedState; } let mappedDispatch: DispatchProps = {} as DispatchProps; @@ -154,6 +149,7 @@ export function connect( Object.entries(mapDispatchToProps).forEach(([key, value]) => { mappedDispatch[key] = (...args: ReduxAction[]) => { store.dispatch(value(...args)); + setState(store.getState()); }; }); } else { @@ -168,10 +164,9 @@ export function connect( }) )(mappedState, mappedDispatch, props); - previous.current.state = store.getState(); + previous.current.state = state; - const node = createElement(Component, mergedProps); - return node; + return createElement(Component, mergedProps); }; if (options.forwardRef) { From c2c794e0238df9654f3c556b61b2ac95aa718794 Mon Sep 17 00:00:00 2001 From: huangxuan Date: Thu, 30 Nov 2023 16:16:37 +0800 Subject: [PATCH 5/8] =?UTF-8?q?[inulax]=20=E4=BF=AE=E5=A4=8D=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E7=AE=A1=E7=90=86=E5=99=A8createStore=E4=BC=A0?= =?UTF-8?q?=E5=85=A5enhancer=E4=B8=8D=E7=94=9F=E6=95=88=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/inula/src/inulax/adapters/redux.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/inula/src/inulax/adapters/redux.ts b/packages/inula/src/inulax/adapters/redux.ts index dbcb6924..c84b7eb3 100644 --- a/packages/inula/src/inulax/adapters/redux.ts +++ b/packages/inula/src/inulax/adapters/redux.ts @@ -133,12 +133,14 @@ export function createStore(reducer: Reducer, preloadedState?: any, enhancers?: dispatch: store.$a.dispatch, }; - enhancers && enhancers(result); - result.dispatch({ type: 'InulaX' }); store.reduxHandler = result; + if (typeof enhancers === 'function') { + return enhancers(createStore)(reducer, preloadedState); + } + return result as ReduxStoreHandler; } @@ -190,12 +192,12 @@ export function bindActionCreators(actionCreators: ActionCreators, dispatch: Dis return boundActionCreators; } -export function compose(...middlewares: ReduxMiddleware[]) { - return (store: ReduxStoreHandler, extraArgument: any) => { - let val; - middlewares.reverse().forEach((middleware: ReduxMiddleware, index) => { +export function compose(...middlewares: ((...args: any[]) => any)[]): (...args: any[]) => T { + return (...args) => { + let val: any; + middlewares.reverse().forEach((middleware, index) => { if (!index) { - val = middleware(store, extraArgument); + val = middleware(...args); return; } val = middleware(val); From 3a30f6ce5972cfa3b1e8f1fe088f382394b4e3eb Mon Sep 17 00:00:00 2001 From: huangxuan Date: Thu, 30 Nov 2023 16:20:14 +0800 Subject: [PATCH 6/8] =?UTF-8?q?[UT]=20=E4=BF=AE=E6=94=B9UT=E8=BF=90?= =?UTF-8?q?=E8=A1=8C=E7=BB=93=E6=9E=9C,=20reduxAdapter=E4=B8=8Eredux?= =?UTF-8?q?=E4=BF=9D=E6=8C=81=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/HorizonXTest/adapters/ReduxAdapter.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/inula/scripts/__tests__/HorizonXTest/adapters/ReduxAdapter.test.tsx b/packages/inula/scripts/__tests__/HorizonXTest/adapters/ReduxAdapter.test.tsx index 38b7445b..499be389 100644 --- a/packages/inula/scripts/__tests__/HorizonXTest/adapters/ReduxAdapter.test.tsx +++ b/packages/inula/scripts/__tests__/HorizonXTest/adapters/ReduxAdapter.test.tsx @@ -187,7 +187,7 @@ describe('Redux adapter', () => { reduxStore.dispatch({ type: 'toggle' }); reduxStore.dispatch({ type: 'toggle' }); - expect(counter).toBe(3); // NOTE: first action is always store initialization + expect(counter).toBe(2); // execute dispatch two times, applyMiddleware was called same times }); it('Should apply multiple enhancers', async () => { @@ -226,7 +226,7 @@ describe('Redux adapter', () => { reduxStore.dispatch({ type: 'toggle' }); - expect(counter).toBe(2); // NOTE: first action is always store initialization + expect(counter).toBe(1); // execute dispatch two times, applyMiddleware was called same times expect(lastAction).toBe('toggle'); expect(middlewareCallList[0]).toBe('callCounter'); expect(middlewareCallList[1]).toBe('lastFunctionStorage'); From 12fae0d2384f05c7d9bc6e8e8c5bae78e4525709 Mon Sep 17 00:00:00 2001 From: huangxuan Date: Thu, 30 Nov 2023 17:00:10 +0800 Subject: [PATCH 7/8] format --- packages/inula/src/inulax/adapters/redux.ts | 4 ++-- packages/inula/src/inulax/adapters/reduxReact.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/inula/src/inulax/adapters/redux.ts b/packages/inula/src/inulax/adapters/redux.ts index c84b7eb3..97ee92f1 100644 --- a/packages/inula/src/inulax/adapters/redux.ts +++ b/packages/inula/src/inulax/adapters/redux.ts @@ -53,8 +53,8 @@ export type ReduxMiddleware = ( type Reducer = (state: any, action: ReduxAction) => any; -type StoreCreator = (reducer: Reducer, preloadedState?: any) => ReduxStoreHandler -type StoreEnhancer = (next: StoreCreator) => StoreCreator +type StoreCreator = (reducer: Reducer, preloadedState?: any) => ReduxStoreHandler; +type StoreEnhancer = (next: StoreCreator) => StoreCreator; function mergeData(state, data) { if (!data) { diff --git a/packages/inula/src/inulax/adapters/reduxReact.ts b/packages/inula/src/inulax/adapters/reduxReact.ts index c404976f..0f74af0c 100644 --- a/packages/inula/src/inulax/adapters/reduxReact.ts +++ b/packages/inula/src/inulax/adapters/reduxReact.ts @@ -92,7 +92,7 @@ type Connector = (Component: OriginalComponent = { areStatesEqual?: (oldState: State, newState: State) => boolean; context?: Context; - forwardRef?: boolean + forwardRef?: boolean; } export function connect( From 4900251b2189fb24226bb690fd9dadda462b15cf Mon Sep 17 00:00:00 2001 From: huangxuan Date: Thu, 30 Nov 2023 17:26:32 +0800 Subject: [PATCH 8/8] =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/inula/src/inulax/adapters/reduxReact.ts | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/inula/src/inulax/adapters/reduxReact.ts b/packages/inula/src/inulax/adapters/reduxReact.ts index 0f74af0c..32eff8d0 100644 --- a/packages/inula/src/inulax/adapters/reduxReact.ts +++ b/packages/inula/src/inulax/adapters/reduxReact.ts @@ -103,7 +103,7 @@ export function connect( dispatchProps, ownProps ): MergedProps => ({ ...stateProps, ...dispatchProps, ...ownProps } as unknown as MergedProps), - options: ConnectOption, + options?: ConnectOption ): Connector { if (!options) { options = {}; @@ -111,14 +111,13 @@ export function connect( //this component should bear the type returned from mapping functions return (Component: OriginalComponent): WrappedComponent => { - const useStore = createStoreHook(options.context || DefaultContext); + const useStore = createStoreHook(options?.context || DefaultContext); //this component should mimic original type of component used const Wrapper: WrappedComponent = (props: OwnProps) => { const store = useStore() as ReduxStoreHandler; const [state, setState] = useState(() => store.getState()); - useEffect(() => { const unsubscribe = store.subscribe(() => { setState(store.getState()); @@ -126,13 +125,13 @@ export function connect( return () => unsubscribe(); }, []); - const previous = useRef<{ state: { [key: string]: any }, mappedState: StateProps }>({ + const previous = useRef<{ state: { [key: string]: any }; mappedState: StateProps }>({ state: {}, mappedState: {} as StateProps, }); let mappedState: StateProps; - if (options.areStatesEqual) { + if (options?.areStatesEqual) { if (options.areStatesEqual(previous.current.state, state)) { mappedState = previous.current.mappedState as StateProps; } else { @@ -169,7 +168,7 @@ export function connect( return createElement(Component, mergedProps); }; - if (options.forwardRef) { + if (options?.forwardRef) { const forwarded = forwardRef((props, ref) => { return Wrapper({ ...props, ref: ref }); });