selectors and store
This commit is contained in:
parent
347d90267e
commit
f322691906
18
src/Updux.ts
18
src/Updux.ts
@ -19,11 +19,18 @@ import { AggregateActions, AggregateSelectors, Dux } from './types.js';
|
||||
import { buildActions } from './buildActions.js';
|
||||
import { buildInitial, AggregateState } from './initial.js';
|
||||
import { buildReducer, MutationCase } from './reducer.js';
|
||||
import { buildEffectsMiddleware, EffectMiddleware } from './effects.js';
|
||||
import { augmentGetState, buildEffectsMiddleware, EffectMiddleware } from './effects.js';
|
||||
import { ToolkitStore } from '@reduxjs/toolkit/dist/configureStore.js';
|
||||
|
||||
type MyActionCreator = { type: string } & ((...args: any) => any);
|
||||
|
||||
type XSel<R> = R extends Function ? R : () => R;
|
||||
type CurriedSelector<S> = S extends (...args: any) => infer R ? XSel<R> : never;
|
||||
|
||||
type CurriedSelectors<S> = {
|
||||
[key in keyof S]: CurriedSelector<S[key]>
|
||||
}
|
||||
|
||||
type ResolveAction<
|
||||
ActionType extends string,
|
||||
ActionArg extends any,
|
||||
@ -176,6 +183,8 @@ export default class Updux<
|
||||
}
|
||||
}
|
||||
|
||||
store.getState = augmentGetState(store.getState, this.selectors);
|
||||
|
||||
return store as ToolkitStore<
|
||||
AggregateState<T_LocalState, T_Subduxes>
|
||||
> & {
|
||||
@ -183,6 +192,12 @@ export default class Updux<
|
||||
ResolveActions<T_LocalActions>,
|
||||
T_Subduxes
|
||||
>;
|
||||
} & {
|
||||
getState: CurriedSelectors<AggregateSelectors<
|
||||
T_LocalSelectors,
|
||||
T_Subduxes,
|
||||
AggregateState<T_LocalState, T_Subduxes>
|
||||
>>
|
||||
};
|
||||
}
|
||||
|
||||
@ -257,3 +272,4 @@ export default class Updux<
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,31 +1,42 @@
|
||||
import { mapValues, map, get } from 'lodash-es';
|
||||
const middlewareFor = (type, middleware) => (api) => (next) => (action) => {
|
||||
if (type !== '*' && action.type !== type)
|
||||
return next(action);
|
||||
if (type !== '*' && action.type !== type) return next(action);
|
||||
return middleware(api)(next)(action);
|
||||
};
|
||||
const sliceMw = (slice, mw) => (api) => {
|
||||
const getSliceState = () => get(api.getState(), slice);
|
||||
return mw(Object.assign(Object.assign({}, api), { getState: getSliceState }));
|
||||
return mw(
|
||||
Object.assign(Object.assign({}, api), { getState: getSliceState }),
|
||||
);
|
||||
};
|
||||
export function augmentMiddlewareApi(api, actions, selectors) {
|
||||
const getState = () => api.getState();
|
||||
const dispatch = (action) => api.dispatch(action);
|
||||
Object.assign(getState, mapValues(selectors, (selector) => {
|
||||
|
||||
export const augmentGetState = (getState, selectors) =>
|
||||
Object.assign(
|
||||
getState,
|
||||
mapValues(selectors, (selector) => {
|
||||
return (...args) => {
|
||||
let result = selector(api.getState());
|
||||
if (typeof result === 'function')
|
||||
return result(...args);
|
||||
if (typeof result === 'function') return result(...args);
|
||||
return result;
|
||||
};
|
||||
}));
|
||||
Object.assign(dispatch, mapValues(actions, (action) => {
|
||||
}),
|
||||
);
|
||||
|
||||
export function augmentMiddlewareApi(api, actions, selectors) {
|
||||
const getState = augmentGetState(() => api.getState(), selectors);
|
||||
const dispatch = (action) => api.dispatch(action);
|
||||
Object.assign(
|
||||
dispatch,
|
||||
mapValues(actions, (action) => {
|
||||
return (...args) => api.dispatch(action(...args));
|
||||
}));
|
||||
return Object.assign(Object.assign({}, api), { getState,
|
||||
}),
|
||||
);
|
||||
return Object.assign(Object.assign({}, api), {
|
||||
getState,
|
||||
dispatch,
|
||||
actions,
|
||||
selectors });
|
||||
selectors,
|
||||
});
|
||||
}
|
||||
export const effectToMiddleware = (effect, actions, selectors) => {
|
||||
let mw = effect;
|
||||
@ -37,12 +48,23 @@ export const effectToMiddleware = (effect, actions, selectors) => {
|
||||
}
|
||||
return (api) => mw(augmentMiddlewareApi(api, actions, selectors));
|
||||
};
|
||||
const composeMw = (mws) => (api) => (original_next) => mws.reduceRight((next, mw) => mw(api)(next), original_next);
|
||||
export function buildMiddleware(effects = [], actions = {}, selectors = {}, sub = {}, wrapper = undefined, dux = undefined) {
|
||||
let inner = map(sub, ({ middleware }, slice) => slice !== '*' && middleware ? sliceMw(slice, middleware) : undefined).filter((x) => x);
|
||||
const local = effects.map((effect) => effectToMiddleware(effect, actions, selectors));
|
||||
const composeMw = (mws) => (api) => (original_next) =>
|
||||
mws.reduceRight((next, mw) => mw(api)(next), original_next);
|
||||
export function buildMiddleware(
|
||||
effects = [],
|
||||
actions = {},
|
||||
selectors = {},
|
||||
sub = {},
|
||||
wrapper = undefined,
|
||||
dux = undefined,
|
||||
) {
|
||||
let inner = map(sub, ({ middleware }, slice) =>
|
||||
slice !== '*' && middleware ? sliceMw(slice, middleware) : undefined,
|
||||
).filter((x) => x);
|
||||
const local = effects.map((effect) =>
|
||||
effectToMiddleware(effect, actions, selectors),
|
||||
);
|
||||
let mws = [...local, ...inner];
|
||||
if (wrapper)
|
||||
mws = wrapper(mws, dux);
|
||||
if (wrapper) mws = wrapper(mws, dux);
|
||||
return composeMw(mws);
|
||||
}
|
||||
|
@ -11,6 +11,17 @@ export interface EffectMiddleware<S = any, D extends Dispatch = Dispatch> {
|
||||
const composeMw = (mws) => (api) => (originalNext) =>
|
||||
mws.reduceRight((next, mw) => mw(api)(next), originalNext);
|
||||
|
||||
export const augmentGetState = (originalGetState, selectors) => {
|
||||
const getState = () => originalGetState();
|
||||
for (const s in selectors) {
|
||||
getState[s] = (...args) => {
|
||||
let result = selectors[s](originalGetState());
|
||||
if (typeof result === 'function') return result(...args);
|
||||
return result;
|
||||
};
|
||||
}
|
||||
return getState;
|
||||
};
|
||||
export function buildEffectsMiddleware(
|
||||
effects = [],
|
||||
actions = {},
|
||||
|
@ -17,6 +17,10 @@ test('basic selectors', () => {
|
||||
initial: { y: 2 },
|
||||
selectors: {
|
||||
getY: ({ y }: { y: number }) => y,
|
||||
getYPlus:
|
||||
({ y }) =>
|
||||
(incr: number) =>
|
||||
(y + incr) as number,
|
||||
},
|
||||
}),
|
||||
},
|
||||
@ -29,4 +33,10 @@ test('basic selectors', () => {
|
||||
|
||||
expect(foo.selectors.getY(sample)).toBe(3);
|
||||
expect(foo.selectors.getX(sample)).toBe(4);
|
||||
|
||||
expect(foo.selectors.getYPlus(sample)(3)).toBe(6);
|
||||
|
||||
const store = foo.createStore();
|
||||
expect(store.getState.getY()).toBe(2);
|
||||
expect(store.getState.getYPlus(3)).toBe(5);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user