Skip to content

Commit d9dabc7

Browse files
authored
Merge pull request #7 from jaystack/type-enhancements
Type enhancements
2 parents de951ab + 1a66631 commit d9dabc7

8 files changed

Lines changed: 65 additions & 92 deletions

File tree

README.md

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,6 @@ A repatch middleware takes the `store` instance, a `next` function and the previ
9090
Middleware: Store -> Next -> Reducer -> any
9191
```
9292

93-
where
94-
95-
```javascript
96-
Next: Reducer -> Reducer
97-
```
98-
9993
Use the `addMiddleware` method to chaining middlewares:
10094

10195
```javascript

docs/store.md

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
The `Store` holds the whole state tree of your application. You need to instantiate it.
44

5-
## Constructor
5+
## **`constructor(initialState: State)`**
66

77
#### Arguments
88

@@ -22,7 +22,7 @@ const store = new Store({ counter: 0 })
2222

2323
## Methods
2424

25-
### `getState()`
25+
### **`getState(): State`**
2626

2727
This method returns the current state of the store.
2828

@@ -40,17 +40,17 @@ const state = store.getState()
4040
console.log(state.counter === 0) // true
4141
```
4242

43-
### `dispatch(reducer)`
43+
### **`dispatch(reducer: Reducer): State`**
4444

4545
Dispatches a reducer.
4646

4747
#### Arguments
4848

49-
1) **reducer** (*ReducerFunction: State -> State*): That reducer will reduce the state of the store. This takes the current state and returns the next state.
49+
1) **reducer** (*Reducer: State -> State*): That reducer will reduce the state of the store. This takes the current state and returns the next state.
5050

5151
#### Returns
5252

53-
(*ReducerFunction*): The final reducer that is made by applying the middlewares - if they are given. If the store does enhanced with middlewares, the `dispatch` method returns the same reducer that was taken as argument.
53+
(*State*): The new state after reducing.
5454

5555
#### Example
5656

@@ -66,19 +66,19 @@ console.log(result === increment) // true
6666

6767
#### Notes
6868

69-
Middlewares can modify the given reducer, so that is not guaranteed that the `dispatch` returns the original reducer that was taken as argument.
69+
If you use middlewares, that is not guaranteed that the `dispatch` returns the new state. For example `thunk` middleware modify `dispatch` that it returns the result of delegate function.
7070

71-
### `subscribe(listener)`
71+
### **`subscribe(listener: Listener): Unsubscribe`**
7272

7373
Adds a state change listener.
7474

7575
#### Arguments
7676

77-
1) **listener** (*ListenerFunction: void -> void*): The listener that will be synchronously run after the state was modified by dispatching a reducer.
77+
1) **listener** (*Listener: void -> void*): The listener that will be synchronously run after the state was modified by dispatching a reducer.
7878

7979
#### Returns
8080

81-
(*UnsubscribeFunction: void -> void*): The unsubscribe function, that you can use to unsubscribe the given listener.
81+
(*Unsubscribe: void -> void*): The unsubscribe function, that you can use to unsubscribe the given listener.
8282

8383
#### Example
8484

@@ -96,15 +96,15 @@ unsubscribe()
9696
store.dispatch(increment) // listener won't be fired
9797
```
9898

99-
### `addMiddleware(...middlewares)`
99+
### **`addMiddleware(...middlewares: Middleware[]): Store`**
100100

101101
Enhances the store with the given middleware(s).
102102

103103
Middlewares will be run at dispatching before the store applies the new state of the reducer. The added middlewares are composed by order of addition, so the last added middleware will run first.
104104

105105
#### Arguments
106106

107-
1) **...middlewares** (*MiddlewareFunction: Store -> Next -> Reducer -> any*): Middlewares as variadic arguments. Middleware functions take the `store` instance, a `next` function and the previous `reducer`. The middleware can provide a new reducer via the `next` function.
107+
1) **...middlewares** (*Middleware: Store -> Next -> Reducer -> any*): Middlewares as variadic arguments. Middleware functions take the `store` instance, a `next` function and the previous `reducer`. The middleware can provide a new reducer via the `next` function.
108108

109109
#### Returns
110110

@@ -124,10 +124,4 @@ const store = new Store({ counter: 0 }).addMiddleware(logger)
124124

125125
store.dispatch(state => ({ counter: state.counter + 1 }))
126126
// logger logs { counter: 0 } { counter: 1 }
127-
```
128-
129-
## Static members
130-
131-
### `thunk`
132-
133-
The [thunk](thunk.md) middleware.
127+
```

docs/thunk.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
Thunk is a repatch middleware to handle async actions. It is very similar to [redux-thunk](https://www.npmjs.com/package/redux-thunk). Thunk middleware allows you to create reducers that returns a function (delegate). The delegate will be fired at dispatching.
44

5-
## ThunkMiddleware
6-
7-
`State -> Delegate`
5+
`Thunk: State -> Delegate`
86

97
#### Example
108

src/store/index.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,24 @@
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> = 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);
2420
this.listeners.forEach(listener => listener());
25-
return <R>reducer;
21+
return this.state;
2622
};
2723

2824
subscribe = (listener: Listener): Unsubscribe => {
@@ -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: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,34 @@
1-
import { Middleware, GetState, Dispatch, Reducer, Store } from '../types';
1+
import { Middleware, Dispatch, GetState } from '../types';
22

3-
export interface ThunkMiddleware<State, ExtraArgument>
4-
extends Middleware<State, Reducer<State>, Thunk<State, ExtraArgument, any>> {
5-
withExtraArgument: <EA>(extraArgument: EA) => ThunkMiddleware<State, EA>;
3+
export interface Delegate<S, E, R> {
4+
(dispatch: Dispatch<S>, getState: GetState<S>, extraArgument: E): R;
65
}
76

8-
export interface Delegate<State, ExtraArgument, Return> {
9-
(dispatch: ThunkDispatch<State, ExtraArgument>, getState: GetState<State>, extraArgument: ExtraArgument): Return;
7+
export interface Thunk<S, E, R> {
8+
(state: S): Delegate<S, E, R>;
109
}
1110

12-
export interface ThunkDispatch<State, ExtraArgument> extends Dispatch<Reducer<State>> {
13-
<Return>(reducer: Thunk<State, ExtraArgument, Return>): Return;
11+
declare module '../types' {
12+
export interface Dispatch<S> {
13+
<R, E = any>(thunk: Thunk<S, E, R>): R;
14+
}
1415
}
1516

16-
export interface Thunk<State, ExtraArgument, Return> {
17-
(state: State): Delegate<State, ExtraArgument, Return>;
17+
export interface ThunkMiddleware<E> extends Middleware {
18+
withExtraArgument: <E>(extraArgument: E) => ThunkMiddleware<E>;
1819
}
1920

20-
const thunkFactory = (extraArgument?) => {
21-
const thunk = store => next => reducer => {
22-
if (typeof reducer !== 'function') throw new Error('Thunk requires reducers as functions');
23-
const state = store.getState();
24-
const result = reducer(state);
21+
const thunkFactory = <E>(extraArgument?: E): ThunkMiddleware<E> => {
22+
const thunk = (store => next => reducer => {
23+
if (typeof reducer !== 'function') throw new Error('Thunk reducer must return a function');
24+
const result = reducer(store.getState());
2525
if (typeof result === 'function') return result(store.dispatch, store.getState, extraArgument);
26-
else {
27-
next(_ => result);
28-
return reducer;
29-
}
30-
};
31-
thunk['withExtraArgument'] = thunkFactory;
26+
else return next(_ => result);
27+
}) as ThunkMiddleware<E>;
28+
29+
thunk.withExtraArgument = thunkFactory;
30+
3231
return thunk;
3332
};
3433

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

src/store/types.ts

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
export interface GetState<State> {
2-
(): State;
1+
export interface GetState<S> {
2+
(): 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<Reducer> {
10-
(reducer: Reducer): Reducer;
9+
export interface Dispatch<S> {
10+
(reducer: Reducer<S>): S;
1111
}
1212

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

21-
export interface Middleware<State, R1, R2> {
22-
(store: Store<State, R1>): {
23-
(next: Dispatch<R1>): {
24-
(reducer: R2): any;
25-
};
26-
};
21+
export interface Middleware {
22+
<S>(store: Store<S>): (next: Dispatch<S>) => Dispatch<S>;
2723
}
2824

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

src/test/store.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ describe('store', () => {
3636
assert.strictEqual(store.getState(), 2);
3737
});
3838

39-
it('dispatch returns the reducer', () => {
39+
it('dispatch returns the new state', () => {
4040
const store = new Store(1);
4141
const reducer = state => state + 1;
42-
assert.strictEqual(store.dispatch(reducer), reducer);
42+
assert.strictEqual(store.dispatch(reducer), 2);
4343
});
4444
});
4545

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)