From eb2938ca7b639af8d85e17627de03a3e3ac35ac9 Mon Sep 17 00:00:00 2001 From: Scott Kyle Date: Mon, 22 Apr 2019 23:34:04 -0700 Subject: [PATCH 1/2] Infer action types from combineReducers This change allows for `combineReducers` to completely infer both the state and action types for its returned reducer. From experience with large TypeScript projects, it's common to see that the action type is not explicitly specified, which results in `AnyAction` in the resulting reducer type. Unfortunately, this will propagate through the type inference for `createStore` resulting in `dispatch` being very weakly typed. This change alone causes a chain reaction of a more correctly (and strongly) typed project with regards to Redux. --- index.d.ts | 13 +++++++------ test/typescript/reducers.ts | 21 +++++++++++---------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/index.d.ts b/index.d.ts index c1a16080bf..114330e29e 100644 --- a/index.d.ts +++ b/index.d.ts @@ -90,12 +90,13 @@ export type ReducersMapObject = { * @returns A reducer function that invokes every reducer inside the passed * object, and builds a state object with the same shape. */ -export function combineReducers( - reducers: ReducersMapObject -): Reducer -export function combineReducers( - reducers: ReducersMapObject -): Reducer +export function combineReducers>( + reducers: T +): Reducer, InferActionTypes>> + +type InferActionTypes = R extends Reducer ? A : AnyAction; +type InferReducerTypes = T extends Record ? R : Reducer; +type InferStateType = T extends ReducersMapObject ? S : never; /* store */ diff --git a/test/typescript/reducers.ts b/test/typescript/reducers.ts index 29376714f3..eb03eaf6d7 100644 --- a/test/typescript/reducers.ts +++ b/test/typescript/reducers.ts @@ -42,7 +42,7 @@ function simple() { // Combined reducer also accepts any action. const combined = combineReducers({ sub: reducer }) - let cs: { sub: State } = combined(undefined, { type: 'init' }) + let cs = combined(undefined, { type: 'init' }) cs = combined(cs, { type: 'INCREMENT', count: 10 }) // Combined reducer's state is strictly checked. @@ -110,17 +110,18 @@ function discriminated() { // typings:expect-error s = reducer(s, { type: 'SOME_OTHER_TYPE', someField: 'value' }) - // Combined reducer accepts any action by default which allows to include - // third-party reducers without the need to add their actions to the union. - const combined = combineReducers({ sub: reducer }) + // Combined reducer accepts a union actions types accepted each reducer, + // which can be very permissive for unknown third-party reducers. + const combined = combineReducers({ + sub: reducer, + unknown: (state => state) as Reducer + }) - let cs: { sub: State } = combined(undefined, { type: 'init' }) - cs = combined(cs, { type: 'SOME_OTHER_TYPE' }) + let cs = combined(undefined, { type: 'init' }) + cs = combined(cs, { type: 'SOME_OTHER_TYPE', someField: 'value' }) // Combined reducer can be made to only accept known actions. - const strictCombined = combineReducers<{ sub: State }, MyAction>({ - sub: reducer - }) + const strictCombined = combineReducers({ sub: reducer }) strictCombined(cs, { type: 'INCREMENT' }) // typings:expect-error @@ -179,7 +180,7 @@ function typeGuards() { const combined = combineReducers({ sub: reducer }) - let cs: { sub: State } = combined(undefined, { type: 'init' }) + let cs = combined(undefined, { type: 'init' }) cs = combined(cs, { type: 'INCREMENT', count: 10 }) } From 6fe561452f592fea4ba236103ea049541956f151 Mon Sep 17 00:00:00 2001 From: Tim Dorr Date: Tue, 23 Apr 2019 09:55:36 -0400 Subject: [PATCH 2/2] Fix formatting issues. --- index.d.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.d.ts b/index.d.ts index 114330e29e..1cf5a70433 100644 --- a/index.d.ts +++ b/index.d.ts @@ -94,9 +94,9 @@ export function combineReducers>( reducers: T ): Reducer, InferActionTypes>> -type InferActionTypes = R extends Reducer ? A : AnyAction; -type InferReducerTypes = T extends Record ? R : Reducer; -type InferStateType = T extends ReducersMapObject ? S : never; +type InferActionTypes = R extends Reducer ? A : AnyAction +type InferReducerTypes = T extends Record ? R : Reducer +type InferStateType = T extends ReducersMapObject ? S : never /* store */