Skip to content

Commit 0dd93d1

Browse files
committed
Revamp TypeScript typing with more type safety
1 parent 4acb40c commit 0dd93d1

File tree

5 files changed

+57
-43
lines changed

5 files changed

+57
-43
lines changed

index.d.ts

Lines changed: 44 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
/**
23
* An *action* is a plain object that represents an intention to change the
34
* state. Actions are the only way to get data into the store. Any data,
@@ -12,12 +13,13 @@
1213
* Other than `type`, the structure of an action object is really up to you.
1314
* If you're interested, check out Flux Standard Action for recommendations on
1415
* how actions should be constructed.
16+
*
17+
* @template T the type of the action's `type` tag.
1518
*/
16-
export interface Action {
17-
type: any;
19+
export interface Action<T = any> {
20+
type: T;
1821
}
1922

20-
2123
/* reducers */
2224

2325
/**
@@ -41,15 +43,18 @@ export interface Action {
4143
*
4244
* *Do not put API calls into reducers.*
4345
*
44-
* @template S State object type.
46+
* @template S The type of state consumed and produced by this reducer.
47+
* @template A The type of actions the reducer can potentially respond to.
4548
*/
46-
export type Reducer<S> = <A extends Action>(state: S | undefined, action: A) => S;
49+
export type Reducer<S = {}, A extends Action = Action> = (state: S | undefined, action: A) => S;
4750

4851
/**
4952
* Object whose values correspond to different reducer functions.
53+
*
54+
* @template A The type of actions the reducers can potentially respond to.
5055
*/
51-
export type ReducersMapObject<S> = {
52-
[K in keyof S]: Reducer<S[K]>;
56+
export type ReducersMapObject<S = {}, A extends Action = Action> = {
57+
[K in keyof S]: Reducer<S[K], A>;
5358
}
5459

5560
/**
@@ -70,7 +75,7 @@ export type ReducersMapObject<S> = {
7075
* @returns A reducer function that invokes every reducer inside the passed
7176
* object, and builds a state object with the same shape.
7277
*/
73-
export function combineReducers<S>(reducers: ReducersMapObject<S>): Reducer<S>;
78+
export function combineReducers<S, A extends Action>(reducers: ReducersMapObject<S, A>): Reducer<S, A>;
7479

7580

7681
/* store */
@@ -92,9 +97,12 @@ export function combineReducers<S>(reducers: ReducersMapObject<S>): Reducer<S>;
9297
* function to handle async actions in addition to actions. Middleware may
9398
* transform, delay, ignore, or otherwise interpret actions or async actions
9499
* before passing them to the next middleware.
100+
*
101+
* @template S unused, here only for backwards compatibility.
102+
* @template D the type of things (actions or otherwise) which may be dispatched.
95103
*/
96-
export interface Dispatch<S> {
97-
<A extends Action>(action: A): A;
104+
export interface Dispatch<S = any, D = Action> {
105+
<A extends D>(action: A): A;
98106
}
99107

100108
/**
@@ -109,9 +117,11 @@ export interface Unsubscribe {
109117
* There should only be a single store in a Redux app, as the composition
110118
* happens on the reducer level.
111119
*
112-
* @template S State object type.
120+
* @template S The type of state held by this store.
121+
* @template A the type of actions which may be dispatched by this store.
122+
* @template N The type of non-actions which may be dispatched by this store.
113123
*/
114-
export interface Store<S> {
124+
export interface Store<S = {}, A extends Action = Action, N = never> {
115125
/**
116126
* Dispatches an action. It is the only way to trigger a state change.
117127
*
@@ -138,7 +148,7 @@ export interface Store<S> {
138148
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
139149
* return something else (for example, a Promise you can await).
140150
*/
141-
dispatch: Dispatch<S>;
151+
dispatch: Dispatch<any, A | N>;
142152

143153
/**
144154
* Reads the state tree managed by the store.
@@ -182,7 +192,7 @@ export interface Store<S> {
182192
*
183193
* @param nextReducer The reducer for the store to use instead.
184194
*/
185-
replaceReducer(nextReducer: Reducer<S>): void;
195+
replaceReducer(nextReducer: Reducer<S, A>): void;
186196
}
187197

188198
/**
@@ -191,11 +201,13 @@ export interface Store<S> {
191201
* `createStore(reducer, preloadedState)` exported from the Redux package, from
192202
* store creators that are returned from the store enhancers.
193203
*
194-
* @template S State object type.
204+
* @template S The type of state to be held by the store.
205+
* @template A The type of actions which may be dispatched.
206+
* @template D The type of all things which may be dispatched.
195207
*/
196208
export interface StoreCreator {
197-
<S>(reducer: Reducer<S>, enhancer?: StoreEnhancer<S>): Store<S>;
198-
<S>(reducer: Reducer<S>, preloadedState: S, enhancer?: StoreEnhancer<S>): Store<S>;
209+
<S, A extends Action, N>(reducer: Reducer<S, A>, enhancer?: StoreEnhancer<N>): Store<S, A, N>;
210+
<S, A extends Action, N>(reducer: Reducer<S, A>, preloadedState: S, enhancer?: StoreEnhancer<N>): Store<S, A, N>;
199211
}
200212

201213
/**
@@ -215,10 +227,11 @@ export interface StoreCreator {
215227
* provided by the developer tools. It is what makes time travel possible
216228
* without the app being aware it is happening. Amusingly, the Redux
217229
* middleware implementation is itself a store enhancer.
230+
*
218231
*/
219-
export type StoreEnhancer<S> = (next: StoreEnhancerStoreCreator<S>) => StoreEnhancerStoreCreator<S>;
220-
export type GenericStoreEnhancer = <S>(next: StoreEnhancerStoreCreator<S>) => StoreEnhancerStoreCreator<S>;
221-
export type StoreEnhancerStoreCreator<S> = (reducer: Reducer<S>, preloadedState?: S) => Store<S>;
232+
export type StoreEnhancer<N = never> = (next: StoreEnhancerStoreCreator<N>) => StoreEnhancerStoreCreator<N>;
233+
export type GenericStoreEnhancer<N = never> = StoreEnhancer<N>;
234+
export type StoreEnhancerStoreCreator<N = never> = <S = any, A extends Action = Action>(reducer: Reducer<S, A>, preloadedState?: S) => Store<S, A, N>;
222235

223236
/**
224237
* Creates a Redux store that holds the state tree.
@@ -253,8 +266,8 @@ export const createStore: StoreCreator;
253266

254267
/* middleware */
255268

256-
export interface MiddlewareAPI<S> {
257-
dispatch: Dispatch<S>;
269+
export interface MiddlewareAPI<S = any, D = Action> {
270+
dispatch: Dispatch<any, D>;
258271
getState(): S;
259272
}
260273

@@ -268,7 +281,7 @@ export interface MiddlewareAPI<S> {
268281
* asynchronous API call into a series of synchronous actions.
269282
*/
270283
export interface Middleware {
271-
<S>(api: MiddlewareAPI<S>): (next: Dispatch<S>) => Dispatch<S>;
284+
<S = any, D = Action>(api: MiddlewareAPI<S, D>): (next: Dispatch<any, D>) => Dispatch<any, D>;
272285
}
273286

274287
/**
@@ -317,8 +330,8 @@ export interface ActionCreator<A> {
317330
/**
318331
* Object whose values are action creator functions.
319332
*/
320-
export interface ActionCreatorsMapObject {
321-
[key: string]: ActionCreator<any>;
333+
export interface ActionCreatorsMapObject<A = any> {
334+
[key: string]: ActionCreator<A>;
322335
}
323336

324337
/**
@@ -340,19 +353,19 @@ export interface ActionCreatorsMapObject {
340353
* creator wrapped into the `dispatch` call. If you passed a function as
341354
* `actionCreator`, the return value will also be a single function.
342355
*/
343-
export function bindActionCreators<A extends ActionCreator<any>>(actionCreator: A, dispatch: Dispatch<any>): A;
356+
export function bindActionCreators<A, C extends ActionCreator<A>>(actionCreator: C, dispatch: Dispatch<any, A>): C;
344357

345358
export function bindActionCreators<
346359
A extends ActionCreator<any>,
347360
B extends ActionCreator<any>
348-
>(actionCreator: A, dispatch: Dispatch<any>): B;
361+
>(actionCreator: A, dispatch: Dispatch<any, any>): B;
349362

350-
export function bindActionCreators<M extends ActionCreatorsMapObject>(actionCreators: M, dispatch: Dispatch<any>): M;
363+
export function bindActionCreators<A, M extends ActionCreatorsMapObject<A>>(actionCreators: M, dispatch: Dispatch<any, A>): M;
351364

352365
export function bindActionCreators<
353-
M extends ActionCreatorsMapObject,
354-
N extends ActionCreatorsMapObject
355-
>(actionCreators: M, dispatch: Dispatch<any>): N;
366+
M extends ActionCreatorsMapObject<any>,
367+
N extends ActionCreatorsMapObject<any>
368+
>(actionCreators: M, dispatch: Dispatch<any, any>): N;
356369

357370

358371
/* compose */

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@
110110
"rollup-plugin-replace": "^1.1.1",
111111
"rollup-plugin-uglify": "^1.0.1",
112112
"rxjs": "^5.0.0-beta.6",
113-
"typescript": "^2.1.0",
113+
"typescript": "^2.4.2",
114114
"typescript-definition-tester": "0.0.5"
115115
},
116116
"npmName": "redux",

test/typescript/compose.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,4 @@ const t11: number = compose(stringToNumber, numberToString, stringToNumber,
3636

3737

3838
const funcs = [stringToNumber, numberToString, stringToNumber];
39-
const t12 = compose(...funcs)('bar', 42, true);
39+
const t12 = compose(...funcs)('bar');

test/typescript/reducers.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,15 @@ interface AddTodoAction extends Action {
1111
}
1212

1313

14-
const todosReducer: Reducer<TodosState> = (state: TodosState,
15-
action: Action): TodosState => {
16-
switch (action.type) {
17-
case 'ADD_TODO':
18-
return [...state, (<AddTodoAction>action).text]
19-
default:
20-
return state
14+
const todosReducer: Reducer<TodosState, AddTodoAction> =
15+
(state = [], action) => {
16+
switch (action.type) {
17+
case 'ADD_TODO':
18+
return [...state, action.text]
19+
default:
20+
return state
21+
}
2122
}
22-
}
2323

2424
const todosState: TodosState = todosReducer([], {
2525
type: 'ADD_TODO',
@@ -47,7 +47,8 @@ type RootState = {
4747
counter: CounterState;
4848
}
4949

50-
const rootReducer: Reducer<RootState> = combineReducers({
50+
51+
const rootReducer = combineReducers<RootState, Action | AddTodoAction>({
5152
todos: todosReducer,
5253
counter: counterReducer,
5354
})

test/typescript/store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const reducer: Reducer<State> = (state: State, action: Action): State => {
1515

1616
/* createStore */
1717

18-
const store: Store<State> = createStore<State>(reducer);
18+
const store: Store<State> = createStore(reducer);
1919

2020
const storeWithPreloadedState: Store<State> = createStore(reducer, {
2121
todos: []

0 commit comments

Comments
 (0)