Merge branch 'mw-getState'
This commit is contained in:
commit
8d4542fffa
5
Changes
5
Changes
@ -1,5 +1,10 @@
|
|||||||
# Revision history for Updux
|
# 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
|
1.1.0 2019-11-05
|
||||||
- Document mapping behavior of the '*' subdux.
|
- Document mapping behavior of the '*' subdux.
|
||||||
- add subduxUpreducer.
|
- add subduxUpreducer.
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
"build": "tsc && typedoc",
|
"build": "tsc && typedoc",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"version": "1.1.0",
|
"version": "1.2.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/yanick/updux.git"
|
"url": "git+https://github.com/yanick/updux.git"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import fp from 'lodash/fp';
|
import fp from 'lodash/fp';
|
||||||
|
|
||||||
import { Middleware, MiddlewareAPI, Dispatch } from 'redux';
|
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 => {
|
const MiddlewareFor = (type: any, mw: Middleware ): Middleware => api => next => action => {
|
||||||
if (type !== '*' && action.type !== type) return 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;
|
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>(
|
function buildMiddleware<S=any>(
|
||||||
effects : Dictionary<Middleware<{},S,UpduxDispatch>>= {},
|
effects : Dictionary<Middleware<{},S,UpduxDispatch>>= {},
|
||||||
actions : Dictionary<ActionCreator>= {},
|
actions : Dictionary<ActionCreator>= {},
|
||||||
subMiddlewares :Middleware<{},S,UpduxDispatch>[] = [],
|
subduxes :any = {},
|
||||||
): Middleware<{},S,UpduxDispatch>
|
): 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>) => {
|
return (api: MiddlewareAPI<UpduxDispatch,S>) => {
|
||||||
|
|
||||||
for (let type in actions) {
|
for (let type in actions) {
|
||||||
|
@ -1,110 +1,142 @@
|
|||||||
import Updux from '.';
|
import Updux from '.';
|
||||||
import u from 'updeep';
|
import u from 'updeep';
|
||||||
|
|
||||||
test( 'simple effect', () => {
|
test('simple effect', () => {
|
||||||
|
const tracer = jest.fn();
|
||||||
|
|
||||||
const tracer = jest.fn();
|
const store = new Updux({
|
||||||
|
effects: {
|
||||||
const store = (new Updux({
|
foo: (api: any) => (next: any) => (action: any) => {
|
||||||
effects: {
|
tracer();
|
||||||
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);
|
|
||||||
next(action);
|
next(action);
|
||||||
};
|
},
|
||||||
|
},
|
||||||
|
}).createStore();
|
||||||
|
|
||||||
const store = (new Updux({
|
expect(tracer).not.toHaveBeenCalled();
|
||||||
effects: {
|
|
||||||
foo: tracerEffect('root'),
|
|
||||||
},
|
|
||||||
subduxes: {
|
|
||||||
zzz: {effects: {
|
|
||||||
foo: tracerEffect('child'),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})).createStore();
|
|
||||||
|
|
||||||
expect(tracer).not.toHaveBeenCalled();
|
store.dispatch({type: 'bar'});
|
||||||
|
|
||||||
store.dispatch({ type: 'bar' });
|
expect(tracer).not.toHaveBeenCalled();
|
||||||
|
|
||||||
expect(tracer).not.toHaveBeenCalled();
|
store.dispatch.foo();
|
||||||
|
|
||||||
store.dispatch.foo();
|
|
||||||
|
|
||||||
expect(tracer).toHaveBeenNthCalledWith(1,'root');
|
|
||||||
expect(tracer).toHaveBeenNthCalledWith(2,'child');
|
|
||||||
|
|
||||||
|
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: {
|
effects: {
|
||||||
'*': api => next => action => {
|
foo: tracerEffect('child'),
|
||||||
tracer();
|
|
||||||
next(action);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})).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) {
|
const store = new Updux({
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
effects: {
|
||||||
}
|
'*': api => next => action => {
|
||||||
|
tracer();
|
||||||
|
next(action);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).createStore();
|
||||||
|
|
||||||
const tracer = jest.fn();
|
expect(tracer).not.toHaveBeenCalled();
|
||||||
|
|
||||||
const store = (new Updux({
|
store.dispatch({type: 'bar'});
|
||||||
effects: {
|
|
||||||
foo: api => next => async action => {
|
|
||||||
next(action);
|
|
||||||
await timeout(1000);
|
|
||||||
tracer();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})).createStore();
|
|
||||||
|
|
||||||
expect(tracer).not.toHaveBeenCalled();
|
expect(tracer).toHaveBeenCalled();
|
||||||
|
});
|
||||||
store.dispatch.foo();
|
|
||||||
|
test('async effect', async () => {
|
||||||
expect(tracer).not.toHaveBeenCalled();
|
function timeout(ms: number) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
await timeout(1000);
|
}
|
||||||
|
|
||||||
expect(tracer).toHaveBeenCalled();
|
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 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
|
* A middleware aggregating all the effects defined in the
|
||||||
* updux and its subduxes. Effects of the updux itself are
|
* updux and its subduxes. Effects of the updux itself are
|
||||||
* done before the subduxes effects.
|
* 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> {
|
@computed get middleware(): Middleware<{}, S, UpduxDispatch> {
|
||||||
return buildMiddleware(
|
return buildMiddleware(
|
||||||
this.localEffects,
|
this.localEffects,
|
||||||
this.actions,
|
this.actions,
|
||||||
Object.values(this.subduxes).map(sd => sd.middleware),
|
this.subduxes,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user