Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 7bea62a

Browse files
pelotomtimdorr
authored andcommittedOct 6, 2017
Revamp TypeScript typing with more type safety (#2563)
* Revamp TypeScript typing with more type safety * Provide a default action type for combineReducers * Change state default types to any * Don't parameterize Dispatch with a state type * Remove docstring about excised type parameter
1 parent 4acb40c commit 7bea62a

File tree

5 files changed

+54
-41
lines changed

5 files changed

+54
-41
lines changed
 

‎index.d.ts

+41-29
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 = any, 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 = any, 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 = Action>(reducers: ReducersMapObject<S, A>): Reducer<S, A>;
7479

7580

7681
/* store */
@@ -92,9 +97,11 @@ 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 D the type of things (actions or otherwise) which may be dispatched.
95102
*/
96-
export interface Dispatch<S> {
97-
<A extends Action>(action: A): A;
103+
export interface Dispatch<D = Action> {
104+
<A extends D>(action: A): A;
98105
}
99106

100107
/**
@@ -109,9 +116,11 @@ export interface Unsubscribe {
109116
* There should only be a single store in a Redux app, as the composition
110117
* happens on the reducer level.
111118
*
112-
* @template S State object type.
119+
* @template S The type of state held by this store.
120+
* @template A the type of actions which may be dispatched by this store.
121+
* @template N The type of non-actions which may be dispatched by this store.
113122
*/
114-
export interface Store<S> {
123+
export interface Store<S = any, A extends Action = Action, N = never> {
115124
/**
116125
* Dispatches an action. It is the only way to trigger a state change.
117126
*
@@ -138,7 +147,7 @@ export interface Store<S> {
138147
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
139148
* return something else (for example, a Promise you can await).
140149
*/
141-
dispatch: Dispatch<S>;
150+
dispatch: Dispatch<A | N>;
142151

143152
/**
144153
* Reads the state tree managed by the store.
@@ -182,7 +191,7 @@ export interface Store<S> {
182191
*
183192
* @param nextReducer The reducer for the store to use instead.
184193
*/
185-
replaceReducer(nextReducer: Reducer<S>): void;
194+
replaceReducer(nextReducer: Reducer<S, A>): void;
186195
}
187196

188197
/**
@@ -191,11 +200,13 @@ export interface Store<S> {
191200
* `createStore(reducer, preloadedState)` exported from the Redux package, from
192201
* store creators that are returned from the store enhancers.
193202
*
194-
* @template S State object type.
203+
* @template S The type of state to be held by the store.
204+
* @template A The type of actions which may be dispatched.
205+
* @template D The type of all things which may be dispatched.
195206
*/
196207
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>;
208+
<S, A extends Action, N>(reducer: Reducer<S, A>, enhancer?: StoreEnhancer<N>): Store<S, A, N>;
209+
<S, A extends Action, N>(reducer: Reducer<S, A>, preloadedState: S, enhancer?: StoreEnhancer<N>): Store<S, A, N>;
199210
}
200211

201212
/**
@@ -215,10 +226,11 @@ export interface StoreCreator {
215226
* provided by the developer tools. It is what makes time travel possible
216227
* without the app being aware it is happening. Amusingly, the Redux
217228
* middleware implementation is itself a store enhancer.
229+
*
218230
*/
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>;
231+
export type StoreEnhancer<N = never> = (next: StoreEnhancerStoreCreator<N>) => StoreEnhancerStoreCreator<N>;
232+
export type GenericStoreEnhancer<N = never> = StoreEnhancer<N>;
233+
export type StoreEnhancerStoreCreator<N = never> = <S = any, A extends Action = Action>(reducer: Reducer<S, A>, preloadedState?: S) => Store<S, A, N>;
222234

223235
/**
224236
* Creates a Redux store that holds the state tree.
@@ -253,8 +265,8 @@ export const createStore: StoreCreator;
253265

254266
/* middleware */
255267

256-
export interface MiddlewareAPI<S> {
257-
dispatch: Dispatch<S>;
268+
export interface MiddlewareAPI<S = any, D = Action> {
269+
dispatch: Dispatch<D>;
258270
getState(): S;
259271
}
260272

@@ -268,7 +280,7 @@ export interface MiddlewareAPI<S> {
268280
* asynchronous API call into a series of synchronous actions.
269281
*/
270282
export interface Middleware {
271-
<S>(api: MiddlewareAPI<S>): (next: Dispatch<S>) => Dispatch<S>;
283+
<S = any, D = Action>(api: MiddlewareAPI<S, D>): (next: Dispatch<D>) => Dispatch<D>;
272284
}
273285

274286
/**
@@ -317,8 +329,8 @@ export interface ActionCreator<A> {
317329
/**
318330
* Object whose values are action creator functions.
319331
*/
320-
export interface ActionCreatorsMapObject {
321-
[key: string]: ActionCreator<any>;
332+
export interface ActionCreatorsMapObject<A = any> {
333+
[key: string]: ActionCreator<A>;
322334
}
323335

324336
/**
@@ -340,18 +352,18 @@ export interface ActionCreatorsMapObject {
340352
* creator wrapped into the `dispatch` call. If you passed a function as
341353
* `actionCreator`, the return value will also be a single function.
342354
*/
343-
export function bindActionCreators<A extends ActionCreator<any>>(actionCreator: A, dispatch: Dispatch<any>): A;
355+
export function bindActionCreators<A, C extends ActionCreator<A>>(actionCreator: C, dispatch: Dispatch<A>): C;
344356

345357
export function bindActionCreators<
346358
A extends ActionCreator<any>,
347359
B extends ActionCreator<any>
348360
>(actionCreator: A, dispatch: Dispatch<any>): B;
349361

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

352364
export function bindActionCreators<
353-
M extends ActionCreatorsMapObject,
354-
N extends ActionCreatorsMapObject
365+
M extends ActionCreatorsMapObject<any>,
366+
N extends ActionCreatorsMapObject<any>
355367
>(actionCreators: M, dispatch: Dispatch<any>): N;
356368

357369

‎package.json

+1-1
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

+1-1
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

+10-9
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

+1-1
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)
Please sign in to comment.