Skip to content

Commit e7d3e2a

Browse files
author
Péter Hauszknecht
committed
- update Dispatch signature
- simplify type annotations - remove static thunk member from Store
1 parent fddaabd commit e7d3e2a

4 files changed

Lines changed: 45 additions & 59 deletions

File tree

src/store/index.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
import { Store as IStore, Listener, Unsubscribe, Middleware, Reducer, Dispatch, GetState } from './types';
2-
import thunk, { ThunkMiddleware } from './middlewares/thunk';
32

43
export * from './types';
54
export * from './middlewares/thunk';
6-
export { thunk };
75

8-
export default class Store<State, R = Reducer<State>> implements IStore<State, R> {
9-
static thunk = thunk;
10-
11-
private state: State;
6+
export default class Store<S> implements IStore<S> {
7+
private state: S;
128
private listeners: Listener[] = [];
139

14-
constructor(initialState: State) {
10+
constructor(initialState: S) {
1511
this.state = initialState;
1612
}
1713

18-
getState: GetState<State> = () => this.state;
14+
getState: GetState<S> = () => this.state;
1915

20-
dispatch: Dispatch<R, State> = reducer => {
16+
dispatch: Dispatch<S> = reducer => {
2117
if (typeof reducer !== 'function')
2218
throw new Error('Reducer is not a function: dispatch takes only reducers as functions.');
2319
this.state = reducer(this.state);
@@ -32,7 +28,7 @@ export default class Store<State, R = Reducer<State>> implements IStore<State, R
3228
return () => (this.listeners = this.listeners.filter(lis => lis !== listener));
3329
};
3430

35-
addMiddleware = <R2>(...middlewares: Middleware<State, R, R2>[]): Store<State, R | R2> => {
31+
addMiddleware = (...middlewares: Middleware[]): this => {
3632
if (middlewares.some(middleware => typeof middleware !== 'function'))
3733
throw new Error('Middleware is not a function: addMiddleware takes only middlewares as functions.');
3834
middlewares.forEach(middleware => (this.dispatch = middleware(this)(this.dispatch) as any));

src/store/middlewares/thunk.ts

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,34 @@
1-
import { Middleware, GetState, Dispatch, Reducer, Store } from '../types';
2-
3-
export interface ThunkMiddleware<S, EA>
4-
extends Middleware<S, Reducer<S>, Thunk<S, EA>> {
5-
(store: Store<S, Reducer<S> | Delegate<S, EA>>): {
6-
(next: Dispatch<Reducer<S>, S>): Dispatch<Delegate<S, EA>>;
7-
};
8-
withExtraArgument: <EA>(extraArgument: EA) => ThunkMiddleware<S, EA>;
1+
import { Middleware, Dispatch, GetState } from '../types';
2+
3+
export interface Delegate<S, E, R> {
4+
(dispatch: Dispatch<S>, getState: GetState<S>, extraArgument: E): R;
95
}
106

11-
export interface Delegate<S, EA, RE = any> {
12-
(dispatch: ThunkDispatch<S, EA>, getState: GetState<S>, extraArgument: EA): RE;
7+
export interface Thunk<S, E, R> {
8+
(state: S): Delegate<S, E, R>;
139
}
1410

15-
export interface Thunk<S, EA, RE = any> {
16-
(state: S): Delegate<S, EA, RE>;
11+
declare module '../types' {
12+
export interface Dispatch<S> {
13+
<R, E = any>(thunk: Thunk<S, E, R>): R;
14+
}
1715
}
1816

19-
export interface ThunkDispatch<S, EA> extends Dispatch<Reducer<S>, S> {
20-
<Return>(thunk: Thunk<S, EA, Return>): Return;
17+
export interface ThunkMiddleware<E> extends Middleware {
18+
withExtraArgument: <E>(extraArgument: E) => ThunkMiddleware<E>;
2119
}
2220

23-
const thunkFactory = <S, EA>(extraArgument?: EA) => {
21+
const thunkFactory = <E>(extraArgument?: E): ThunkMiddleware<E> => {
2422
const thunk = (store => next => reducer => {
25-
if (typeof reducer !== 'function') throw new Error('Thunk requires reducers as functions');
26-
const state = store.getState();
27-
const result = reducer(state);
23+
if (typeof reducer !== 'function') throw new Error('Thunk reducer must return a function');
24+
const result = reducer(store.getState());
2825
if (typeof result === 'function') return result(store.dispatch, store.getState, extraArgument);
29-
else {
30-
next(_ => result);
31-
return reducer;
32-
}
33-
}) as ThunkMiddleware<S, EA>;
26+
else return next(_ => result);
27+
}) as ThunkMiddleware<E>;
28+
3429
thunk.withExtraArgument = thunkFactory;
30+
3531
return thunk;
3632
};
3733

38-
export default thunkFactory() as ThunkMiddleware<any, any>;
34+
export const thunk = thunkFactory();

src/store/types.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ export interface GetState<S> {
22
(): S;
33
}
44

5-
export interface Reducer<State> {
6-
(state: State): State;
5+
export interface Reducer<S> {
6+
(state: S): S;
77
}
88

9-
export interface Dispatch<R, S = any> {
10-
(reducer: R): S;
9+
export interface Dispatch<S> {
10+
(reducer: Reducer<S>): S;
1111
}
1212

1313
export interface Listener {
@@ -18,15 +18,13 @@ export interface Unsubscribe {
1818
(): void;
1919
}
2020

21-
export interface Middleware<S, R1, R2> {
22-
(store: Store<S, R1>): {
23-
(next: Dispatch<R1, S>): Dispatch<R2>;
24-
};
21+
export interface Middleware {
22+
<S>(store: Store<S>): (next: Dispatch<S>) => Dispatch<S>;
2523
}
2624

27-
export interface Store<S, R = Reducer<S>> {
25+
export interface Store<S> {
2826
getState: GetState<S>;
29-
dispatch: Dispatch<R, S>;
27+
dispatch: Dispatch<S>;
3028
subscribe(listener: Listener): Unsubscribe;
31-
addMiddleware<R2>(...middlewares: Middleware<S, R, R2>[]): Store<S, R | R2>;
29+
addMiddleware(...middlewares: Middleware[]): this;
3230
}

src/test/thunk.spec.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,25 @@
11
import 'mocha';
22
import * as assert from 'assert';
3-
import Store, { thunk, Thunk, Reducer } from '../store';
3+
import Store, { thunk } from '../store';
44

55
process.env.NODE_ENV = 'test';
66

77
describe('thunk', () => {
8-
it('as Store static member', () => {
9-
assert.strictEqual(Store.thunk, thunk);
10-
});
11-
128
it('dispatch invokes the delegate', () => {
13-
const store = new Store(1).addMiddleware<Thunk<number, void, any>>(thunk);
9+
const store = new Store(1).addMiddleware(thunk);
1410
assert.strictEqual(store.getState(), 1);
15-
store.dispatch((state => (dispatch, getState) => {
16-
store.dispatch(state => getState() + 1);
17-
}) as Thunk<number, void, void>);
11+
store.dispatch(state => (dispatch, getState) => {
12+
dispatch(state => getState() + 1);
13+
});
1814
assert.strictEqual(store.getState(), 2);
1915
});
2016

21-
it('dispatch returns the delegate', () => {
22-
const store = new Store(1).addMiddleware<Thunk<number, void, any>>(thunk);
17+
it('dispatch returns the result of delegate', () => {
18+
const store = new Store(1).addMiddleware(thunk);
2319
const expected = 8;
2420
assert.strictEqual(
2521
store.dispatch(state => dispatch => {
26-
store.dispatch(state => state + 1);
22+
dispatch(state => state + 1);
2723
return expected;
2824
}),
2925
expected
@@ -32,9 +28,9 @@ describe('thunk', () => {
3228

3329
it('extra arguments are provided', () => {
3430
const add = (a, b) => a + b;
35-
const store = new Store(1).addMiddleware<Thunk<number, typeof add, any>>(thunk.withExtraArgument(add));
31+
const store = new Store(1).addMiddleware(thunk.withExtraArgument(add));
3632
store.dispatch(state => (dispatch, getState, add) => {
37-
store.dispatch(state => add(state, 1));
33+
dispatch(state => add(state, 1));
3834
});
3935
assert.strictEqual(store.getState(), 2);
4036
});

0 commit comments

Comments
 (0)