middleware get their local state
This commit is contained in:
parent
74d29d3126
commit
9b5a7981d5
5
Changes
5
Changes
@ -1,5 +1,10 @@
|
||||
# Revision history for Updux
|
||||
|
||||
1.2.0 2019-11-06
|
||||
- The middleware's 'getState' returns the local state of its updux,
|
||||
instead of the root state. Plus we add `getRootState` to get
|
||||
the root state.
|
||||
|
||||
1.1.0 2019-11-05
|
||||
- Document mapping behavior of the '*' subdux.
|
||||
- add subduxUpreducer.
|
||||
|
@ -26,7 +26,7 @@
|
||||
"build": "tsc && typedoc",
|
||||
"test": "jest"
|
||||
},
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/yanick/updux.git"
|
||||
|
@ -1,7 +1,7 @@
|
||||
import fp from 'lodash/fp';
|
||||
|
||||
import { Middleware, MiddlewareAPI, Dispatch } from 'redux';
|
||||
import { Dictionary, ActionCreator, Action, UpduxDispatch } from '../types';
|
||||
import { Dictionary, ActionCreator, Action, UpduxDispatch, UpduxMiddleware } from '../types';
|
||||
|
||||
const MiddlewareFor = (type: any, mw: Middleware ): Middleware => api => next => action => {
|
||||
if (type !== '*' && action.type !== type) return next(action);
|
||||
@ -11,12 +11,28 @@ const MiddlewareFor = (type: any, mw: Middleware ): Middleware => api => next =>
|
||||
|
||||
type Next = (action: Action) => any;
|
||||
|
||||
function sliceMw( slice: string, mw: Middleware ): Middleware {
|
||||
return (api) => {
|
||||
const getSliceState = () => fp.get(slice, api.getState() );
|
||||
const getRootState = (api as any).getRootState || api.getState;
|
||||
return mw({...api, getState: getSliceState, getRootState} as any )
|
||||
};
|
||||
}
|
||||
|
||||
function buildMiddleware<S=any>(
|
||||
effects : Dictionary<Middleware<{},S,UpduxDispatch>>= {},
|
||||
actions : Dictionary<ActionCreator>= {},
|
||||
subMiddlewares :Middleware<{},S,UpduxDispatch>[] = [],
|
||||
): Middleware<{},S,UpduxDispatch>
|
||||
subduxes :any = {},
|
||||
): UpduxMiddleware<S>
|
||||
{
|
||||
|
||||
const subMiddlewares = fp.flow(
|
||||
fp.mapValues( fp.get('middleware') ),
|
||||
fp.toPairs,
|
||||
fp.filter(x=>x[1]),
|
||||
fp.map( ([ slice, mw ]: [ string, Middleware]) => sliceMw(slice,mw) )
|
||||
)( subduxes );
|
||||
|
||||
return (api: MiddlewareAPI<UpduxDispatch,S>) => {
|
||||
|
||||
for (let type in actions) {
|
||||
|
@ -1,110 +1,142 @@
|
||||
import Updux from '.';
|
||||
import u from 'updeep';
|
||||
|
||||
test( 'simple effect', () => {
|
||||
test('simple effect', () => {
|
||||
const tracer = jest.fn();
|
||||
|
||||
const tracer = jest.fn();
|
||||
|
||||
const store = (new Updux({
|
||||
effects: {
|
||||
foo: (api:any) => (next:any) => (action:any) => {
|
||||
tracer();
|
||||
next(action);
|
||||
},
|
||||
},
|
||||
})).createStore();
|
||||
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
|
||||
store.dispatch({ type: 'bar' });
|
||||
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
|
||||
store.dispatch.foo();
|
||||
|
||||
expect(tracer).toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
test( 'effect and sub-effect', () => {
|
||||
|
||||
const tracer = jest.fn();
|
||||
|
||||
const tracerEffect = ( signature: string ) => ( api:any ) => (next:any) => ( action: any ) => {
|
||||
tracer(signature);
|
||||
const store = new Updux({
|
||||
effects: {
|
||||
foo: (api: any) => (next: any) => (action: any) => {
|
||||
tracer();
|
||||
next(action);
|
||||
};
|
||||
},
|
||||
},
|
||||
}).createStore();
|
||||
|
||||
const store = (new Updux({
|
||||
effects: {
|
||||
foo: tracerEffect('root'),
|
||||
},
|
||||
subduxes: {
|
||||
zzz: {effects: {
|
||||
foo: tracerEffect('child'),
|
||||
}
|
||||
}
|
||||
},
|
||||
})).createStore();
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
store.dispatch({type: 'bar'});
|
||||
|
||||
store.dispatch({ type: 'bar' });
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
|
||||
store.dispatch.foo();
|
||||
|
||||
expect(tracer).toHaveBeenNthCalledWith(1,'root');
|
||||
expect(tracer).toHaveBeenNthCalledWith(2,'child');
|
||||
store.dispatch.foo();
|
||||
|
||||
expect(tracer).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test( '"*" effect', () => {
|
||||
test('effect and sub-effect', () => {
|
||||
const tracer = jest.fn();
|
||||
|
||||
const tracer = jest.fn();
|
||||
const tracerEffect = (signature: string) => (api: any) => (next: any) => (
|
||||
action: any,
|
||||
) => {
|
||||
tracer(signature);
|
||||
next(action);
|
||||
};
|
||||
|
||||
const store = (new Updux({
|
||||
const store = new Updux({
|
||||
effects: {
|
||||
foo: tracerEffect('root'),
|
||||
},
|
||||
subduxes: {
|
||||
zzz: {
|
||||
effects: {
|
||||
'*': api => next => action => {
|
||||
tracer();
|
||||
next(action);
|
||||
},
|
||||
foo: tracerEffect('child'),
|
||||
},
|
||||
})).createStore();
|
||||
},
|
||||
},
|
||||
}).createStore();
|
||||
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
|
||||
store.dispatch({ type: 'bar' });
|
||||
store.dispatch({type: 'bar'});
|
||||
|
||||
expect(tracer).toHaveBeenCalled();
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
|
||||
store.dispatch.foo();
|
||||
|
||||
expect(tracer).toHaveBeenNthCalledWith(1, 'root');
|
||||
expect(tracer).toHaveBeenNthCalledWith(2, 'child');
|
||||
});
|
||||
|
||||
test( 'async effect', async () => {
|
||||
test('"*" effect', () => {
|
||||
const tracer = jest.fn();
|
||||
|
||||
function timeout(ms:number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
const store = new Updux({
|
||||
effects: {
|
||||
'*': api => next => action => {
|
||||
tracer();
|
||||
next(action);
|
||||
},
|
||||
},
|
||||
}).createStore();
|
||||
|
||||
const tracer = jest.fn();
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
|
||||
const store = (new Updux({
|
||||
effects: {
|
||||
foo: api => next => async action => {
|
||||
next(action);
|
||||
await timeout(1000);
|
||||
tracer();
|
||||
},
|
||||
},
|
||||
})).createStore();
|
||||
store.dispatch({type: 'bar'});
|
||||
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
|
||||
store.dispatch.foo();
|
||||
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
|
||||
await timeout(1000);
|
||||
|
||||
expect(tracer).toHaveBeenCalled();
|
||||
expect(tracer).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('async effect', async () => {
|
||||
function timeout(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const tracer = jest.fn();
|
||||
|
||||
const store = new Updux({
|
||||
effects: {
|
||||
foo: api => next => async action => {
|
||||
next(action);
|
||||
await timeout(1000);
|
||||
tracer();
|
||||
},
|
||||
},
|
||||
}).createStore();
|
||||
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
|
||||
store.dispatch.foo();
|
||||
|
||||
expect(tracer).not.toHaveBeenCalled();
|
||||
|
||||
await timeout(1000);
|
||||
|
||||
expect(tracer).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('getState is local', () => {
|
||||
let childState;
|
||||
let rootState;
|
||||
let rootFromChild;
|
||||
|
||||
const child = new Updux({
|
||||
initial: {alpha: 12},
|
||||
effects: {
|
||||
doIt: ({getState,getRootState}) => next => action => {
|
||||
childState = getState();
|
||||
rootFromChild = getRootState();
|
||||
next(action);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const root = new Updux({
|
||||
initial: {beta: 24},
|
||||
subduxes: {child},
|
||||
effects: {
|
||||
doIt: ({getState}) => next => action => {
|
||||
rootState = getState();
|
||||
next(action);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const store = root.createStore();
|
||||
store.dispatch.doIt();
|
||||
|
||||
expect(rootState).toEqual({beta: 24, child: {alpha: 12}});
|
||||
expect(rootFromChild).toEqual({beta: 24, child: {alpha: 12}});
|
||||
expect(childState).toEqual({alpha: 12});
|
||||
});
|
||||
|
11
src/types.ts
11
src/types.ts
@ -178,7 +178,16 @@ export type UpduxConfig<S=any> = {
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
effects?: Dictionary<Middleware<{}, S, UpduxDispatch>>;
|
||||
effects?: Dictionary<UpduxMiddleware<S>>;
|
||||
};
|
||||
|
||||
export type Upreducer<S = any> = (action: Action) => (state: S) => S;
|
||||
|
||||
export interface UpduxMiddlewareAPI<S> {
|
||||
dispatch: UpduxDispatch,
|
||||
getState(): any,
|
||||
getRootState(): S
|
||||
|
||||
}
|
||||
export type UpduxMiddleware<S=any> = (api: UpduxMiddlewareAPI<S> ) => ( next: UpduxDispatch ) => ( action: Action ) => any;
|
||||
|
||||
|
@ -132,12 +132,15 @@ export class Updux<S = any> {
|
||||
* A middleware aggregating all the effects defined in the
|
||||
* updux and its subduxes. Effects of the updux itself are
|
||||
* done before the subduxes effects.
|
||||
* Note that `getState` will always return the state of the
|
||||
* local updux. The function `getRootState` is provided
|
||||
* alongside `getState` to get the root state.
|
||||
*/
|
||||
@computed get middleware(): Middleware<{}, S, UpduxDispatch> {
|
||||
return buildMiddleware(
|
||||
this.localEffects,
|
||||
this.actions,
|
||||
Object.values(this.subduxes).map(sd => sd.middleware),
|
||||
this.subduxes,
|
||||
);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user