!88 [inulax] 状态管理器inulax问题修复

Merge pull request !88 from xuan/main
This commit is contained in:
openInula-robot 2023-12-01 01:51:17 +00:00 committed by Gitee
commit a72be37fcd
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
5 changed files with 116 additions and 82 deletions

View File

@ -187,7 +187,7 @@ describe('Redux adapter', () => {
reduxStore.dispatch({ type: 'toggle' }); reduxStore.dispatch({ type: 'toggle' });
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 () => { it('Should apply multiple enhancers', async () => {
@ -226,7 +226,7 @@ describe('Redux adapter', () => {
reduxStore.dispatch({ type: 'toggle' }); 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(lastAction).toBe('toggle');
expect(middlewareCallList[0]).toBe('callCounter'); expect(middlewareCallList[0]).toBe('callCounter');
expect(middlewareCallList[1]).toBe('lastFunctionStorage'); expect(middlewareCallList[1]).toBe('lastFunctionStorage');

View File

@ -67,18 +67,50 @@ export function isPromise(obj: any): boolean {
return isObject(obj) && typeof obj.then === 'function'; return isObject(obj) && typeof obj.then === 'function';
} }
export function isSame(x, y) { export function isSame(x: unknown, y: unknown): boolean {
if (typeof Object.is !== 'function') { // 如果两个对象是同一个引用直接返回true
if (x === y) { if (x === y) {
// +0 != -0 return true;
return x !== 0 || 1 / x === 1 / y;
} else {
// NaN == NaN
return x !== x && y !== y;
}
} else {
return Object.is(x, y);
} }
// 如果两个对象类型不同直接返回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) { export function getDetailedType(val: any) {

View File

@ -27,12 +27,12 @@ export {
createDispatchHook, createDispatchHook,
} from './reduxReact'; } from './reduxReact';
export type ReduxStoreHandler = { export type ReduxStoreHandler<T = any> = {
reducer: (state: any, action: { type: string }) => any; reducer(state: T, action: { type: string }): any;
dispatch: (action: { type: string }) => void; dispatch(action: { type: string }): void;
getState: () => any; getState(): T;
subscribe: (listener: () => void) => () => void; subscribe(listener: () => void): () => void;
replaceReducer: (reducer: (state: any, action: { type: string }) => any) => void; replaceReducer(reducer: (state: T, action: { type: string }) => any): void;
}; };
export type ReduxAction = { export type ReduxAction = {
@ -53,6 +53,9 @@ export type ReduxMiddleware = (
type Reducer = (state: any, action: ReduxAction) => any; type Reducer = (state: any, action: ReduxAction) => any;
type StoreCreator = (reducer: Reducer, preloadedState?: any) => ReduxStoreHandler;
type StoreEnhancer = (next: StoreCreator) => StoreCreator;
function mergeData(state, data) { function mergeData(state, data) {
if (!data) { if (!data) {
state.stateWrapper = data; state.stateWrapper = data;
@ -87,7 +90,7 @@ function mergeData(state, data) {
state.stateWrapper = 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({ const store = createStoreX({
id: 'defaultStore', id: 'defaultStore',
state: { stateWrapper: preloadedState }, state: { stateWrapper: preloadedState },
@ -130,12 +133,14 @@ export function createStore(reducer: Reducer, preloadedState?: any, enhancers?):
dispatch: store.$a.dispatch, dispatch: store.$a.dispatch,
}; };
enhancers && enhancers(result);
result.dispatch({ type: 'InulaX' }); result.dispatch({ type: 'InulaX' });
store.reduxHandler = result; store.reduxHandler = result;
if (typeof enhancers === 'function') {
return enhancers(createStore)(reducer, preloadedState);
}
return result as ReduxStoreHandler; return result as ReduxStoreHandler;
} }
@ -150,19 +155,23 @@ export function combineReducers(reducers: { [key: string]: Reducer }): Reducer {
}; };
} }
function applyMiddlewares(store: ReduxStoreHandler, middlewares: ReduxMiddleware[]): void { function applyMiddlewares(createStore: StoreCreator, middlewares: ReduxMiddleware[]): StoreCreator {
middlewares = middlewares.slice(); return (reducer, preloadedState) => {
middlewares.reverse(); middlewares = middlewares.slice();
let dispatch = store.dispatch; middlewares.reverse();
middlewares.forEach(middleware => { const storeObj = createStore(reducer, preloadedState);
dispatch = middleware(store)(dispatch); let dispatch = storeObj.dispatch;
}); middlewares.forEach(middleware => {
store.dispatch = dispatch; dispatch = middleware(storeObj)(dispatch);
});
storeObj.dispatch = dispatch;
return storeObj;
};
} }
export function applyMiddleware(...middlewares: ReduxMiddleware[]): (store: ReduxStoreHandler) => void { export function applyMiddleware(...middlewares: ReduxMiddleware[]): (createStore: StoreCreator) => StoreCreator {
return store => { return createStore => {
return applyMiddlewares(store, middlewares); return applyMiddlewares(createStore, middlewares);
}; };
} }
@ -170,7 +179,7 @@ type ActionCreator = (...params: any[]) => ReduxAction;
type ActionCreators = { [key: string]: ActionCreator }; type ActionCreators = { [key: string]: ActionCreator };
export type BoundActionCreator = (...params: any[]) => void; export type BoundActionCreator = (...params: any[]) => void;
type BoundActionCreators = { [key: string]: BoundActionCreator }; type BoundActionCreators = { [key: string]: BoundActionCreator };
type Dispatch = (action) => any; type Dispatch = (action: ReduxAction) => any;
export function bindActionCreators(actionCreators: ActionCreators, dispatch: Dispatch): BoundActionCreators { export function bindActionCreators(actionCreators: ActionCreators, dispatch: Dispatch): BoundActionCreators {
const boundActionCreators = {}; const boundActionCreators = {};
@ -183,12 +192,12 @@ export function bindActionCreators(actionCreators: ActionCreators, dispatch: Dis
return boundActionCreators; return boundActionCreators;
} }
export function compose(...middlewares: ReduxMiddleware[]) { export function compose<T = StoreCreator>(...middlewares: ((...args: any[]) => any)[]): (...args: any[]) => T {
return (store: ReduxStoreHandler, extraArgument: any) => { return (...args) => {
let val; let val: any;
middlewares.reverse().forEach((middleware: ReduxMiddleware, index) => { middlewares.reverse().forEach((middleware, index) => {
if (!index) { if (!index) {
val = middleware(store, extraArgument); val = middleware(...args);
return; return;
} }
val = middleware(val); val = middleware(val);

View File

@ -41,29 +41,27 @@ export function createStoreHook(context: Context): () => ReduxStoreHandler {
}; };
} }
export function createSelectorHook(context: Context): (selector?: (any) => any) => any { export function createSelectorHook(context: Context): (selector?: ((state: unknown) => any) | undefined) => any {
const store = createStoreHook(context)() as unknown as ReduxStoreHandler; const store = createStoreHook(context)();
return function (selector = state => state) { return function useSelector(selector = state => state) {
const [b, fr] = useState(false); const [state, setState] = useState(() => store.getState());
useEffect(() => { useEffect(() => {
const unsubscribe = store.subscribe(() => fr(!b)); const unsubscribe = store.subscribe(() => {
return () => { setState(store.getState());
unsubscribe(); });
}; return () => unsubscribe();
}); }, []);
return selector(store.getState()); return selector(state);
}; };
} }
export function createDispatchHook(context: Context): () => BoundActionCreator { export function createDispatchHook(context: Context): () => BoundActionCreator {
const store = createStoreHook(context)() as unknown as ReduxStoreHandler; const store = createStoreHook(context)();
return function () { return function useDispatch() {
return action => { return store.dispatch;
store.dispatch(action); };
};
}.bind(store);
} }
export const useSelector = selector => { export const useSelector = selector => {
@ -94,7 +92,7 @@ type Connector<OwnProps, MergedProps> = (Component: OriginalComponent<MergedProp
type ConnectOption<State = any> = { type ConnectOption<State = any> = {
areStatesEqual?: (oldState: State, newState: State) => boolean; areStatesEqual?: (oldState: State, newState: State) => boolean;
context?: Context; context?: Context;
forwardRef?: boolean forwardRef?: boolean;
} }
export function connect<StateProps, DispatchProps, OwnProps, MergedProps>( export function connect<StateProps, DispatchProps, OwnProps, MergedProps>(
@ -105,7 +103,7 @@ export function connect<StateProps, DispatchProps, OwnProps, MergedProps>(
dispatchProps, dispatchProps,
ownProps ownProps
): MergedProps => ({ ...stateProps, ...dispatchProps, ...ownProps } as unknown as MergedProps), ): MergedProps => ({ ...stateProps, ...dispatchProps, ...ownProps } as unknown as MergedProps),
options: ConnectOption, options?: ConnectOption
): Connector<OwnProps, MergedProps> { ): Connector<OwnProps, MergedProps> {
if (!options) { if (!options) {
options = {}; options = {};
@ -117,37 +115,31 @@ export function connect<StateProps, DispatchProps, OwnProps, MergedProps>(
//this component should mimic original type of component used //this component should mimic original type of component used
const Wrapper: WrappedComponent<OwnProps> = (props: OwnProps) => { const Wrapper: WrappedComponent<OwnProps> = (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(() => { useEffect(() => {
const unsubscribe = store.subscribe(() => forceReload(!f)); const unsubscribe = store.subscribe(() => {
return () => { setState(store.getState());
unsubscribe(); });
}; return () => unsubscribe();
}); }, []);
const previous = useRef({ const previous = useRef<{ state: { [key: string]: any }; mappedState: StateProps }>({
state: {}, state: {},
mappedState: {}, mappedState: {} as StateProps,
}) as { });
current: {
state: { [key: string]: any };
mappedState: StateProps;
};
};
let mappedState: StateProps; let mappedState: StateProps;
if (options?.areStatesEqual) { if (options?.areStatesEqual) {
if (options.areStatesEqual(previous.current.state, store.getState())) { if (options.areStatesEqual(previous.current.state, state)) {
mappedState = previous.current.mappedState as StateProps; mappedState = previous.current.mappedState as StateProps;
} else { } else {
mappedState = mapStateToProps ? mapStateToProps(store.getState(), props) : ({} as StateProps); mappedState = mapStateToProps ? mapStateToProps(state, props) : ({} as StateProps);
previous.current.mappedState = mappedState; previous.current.mappedState = mappedState;
} }
} else { } else {
mappedState = mapStateToProps ? mapStateToProps(store.getState(), props) : ({} as StateProps); mappedState = mapStateToProps ? mapStateToProps(state, props) : ({} as StateProps);
previous.current.mappedState = mappedState; previous.current.mappedState = mappedState;
} }
let mappedDispatch: DispatchProps = {} as DispatchProps; let mappedDispatch: DispatchProps = {} as DispatchProps;
@ -156,12 +148,14 @@ export function connect<StateProps, DispatchProps, OwnProps, MergedProps>(
Object.entries(mapDispatchToProps).forEach(([key, value]) => { Object.entries(mapDispatchToProps).forEach(([key, value]) => {
mappedDispatch[key] = (...args: ReduxAction[]) => { mappedDispatch[key] = (...args: ReduxAction[]) => {
store.dispatch(value(...args)); store.dispatch(value(...args));
setState(store.getState());
}; };
}); });
} else { } else {
mappedDispatch = mapDispatchToProps(store.dispatch, props); mappedDispatch = mapDispatchToProps(store.dispatch, props);
} }
} }
mappedDispatch = Object.assign({}, mappedDispatch, { dispatch: store.dispatch });
const mergedProps = ( const mergedProps = (
mergeProps || mergeProps ||
((state, dispatch, originalProps) => { ((state, dispatch, originalProps) => {
@ -169,13 +163,12 @@ export function connect<StateProps, DispatchProps, OwnProps, MergedProps>(
}) })
)(mappedState, mappedDispatch, props); )(mappedState, mappedDispatch, props);
previous.current.state = store.getState(); previous.current.state = state;
const node = createElement(Component, mergedProps); return createElement(Component, mergedProps);
return node;
}; };
if (options.forwardRef) { if (options?.forwardRef) {
const forwarded = forwardRef((props, ref) => { const forwarded = forwardRef((props, ref) => {
return Wrapper({ ...props, ref: ref }); return Wrapper({ ...props, ref: ref });
}); });

View File

@ -35,5 +35,5 @@ function createThunkMiddleware(extraArgument?: any): ReduxMiddleware {
} }
export const thunk = createThunkMiddleware(); export const thunk = createThunkMiddleware();
// @ts-ignore
thunk.withExtraArgument = createThunkMiddleware; export const withExtraArgument = createThunkMiddleware;