commit
a72be37fcd
|
@ -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');
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 });
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue