Skip to content

Commit 23f7beb

Browse files
cellogtimdorr
authored andcommitted
merge type definitions from index.d.ts into combineReducers (reduxjs#3539)
1 parent 5a5c196 commit 23f7beb

File tree

1 file changed

+99
-14
lines changed

1 file changed

+99
-14
lines changed

src/combineReducers.ts

Lines changed: 99 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,84 @@
1-
import {
2-
AnyAction,
3-
Action,
4-
ReducersMapObject,
5-
StateFromReducersMapObject
6-
} from '..'
1+
import { AnyAction, Action, Reducer } from '..'
72
import ActionTypes from './utils/actionTypes'
83
import warning from './utils/warning'
94
import isPlainObject from './utils/isPlainObject'
105

6+
/**
7+
* Internal "virtual" symbol used to make the `CombinedState` type unique.
8+
*/
9+
declare const $CombinedState: unique symbol
10+
11+
/**
12+
* State base type for reducers created with `combineReducers()`.
13+
*
14+
* This type allows the `createStore()` method to infer which levels of the
15+
* preloaded state can be partial.
16+
*
17+
* Because Typescript is really duck-typed, a type needs to have some
18+
* identifying property to differentiate it from other types with matching
19+
* prototypes for type checking purposes. That's why this type has the
20+
* `$CombinedState` symbol property. Without the property, this type would
21+
* match any object. The symbol doesn't really exist because it's an internal
22+
* (i.e. not exported), and internally we never check its value. Since it's a
23+
* symbol property, it's not expected to be unumerable, and the value is
24+
* typed as always undefined, so its never expected to have a meaningful
25+
* value anyway. It just makes this type distinquishable from plain `{}`.
26+
*/
27+
export type CombinedState<S> = { readonly [$CombinedState]?: undefined } & S
28+
29+
/**
30+
* Object whose values correspond to different reducer functions.
31+
*
32+
* @template A The type of actions the reducers can potentially respond to.
33+
*/
34+
export type ReducersMapObject<S = any, A extends Action = Action> = {
35+
[K in keyof S]: Reducer<S[K], A>
36+
}
37+
38+
/**
39+
* Infer a combined state shape from a `ReducersMapObject`.
40+
*
41+
* @template M Object map of reducers as provided to `combineReducers(map: M)`.
42+
*/
43+
export type StateFromReducersMapObject<M> = M extends ReducersMapObject<
44+
any,
45+
any
46+
>
47+
? { [P in keyof M]: M[P] extends Reducer<infer S, any> ? S : never }
48+
: never
49+
50+
/**
51+
* Infer action type from a reducer function.
52+
*
53+
* @template R Type of reducer.
54+
*/
55+
export type ActionFromReducer<R> = R extends Reducer<any, infer A> ? A : never
56+
57+
/**
58+
* Infer action union type from a `ReducersMapObject`.
59+
*
60+
* @template M Object map of reducers as provided to `combineReducers(map: M)`.
61+
*/
62+
export type ActionFromReducersMapObject<M> = M extends ReducersMapObject<
63+
any,
64+
any
65+
>
66+
? ActionFromReducer<ReducerFromReducersMapObject<M>>
67+
: never
68+
69+
/**
70+
* Infer reducer union type from a `ReducersMapObject`.
71+
*
72+
* @template M Object map of reducers as provided to `combineReducers(map: M)`.
73+
*/
74+
export type ReducerFromReducersMapObject<M> = M extends {
75+
[P in keyof M]: infer R
76+
}
77+
? R extends Reducer<any, any>
78+
? R
79+
: never
80+
: never
81+
1182
function getUndefinedStateErrorMessage(key: string, action: Action) {
1283
const actionType = action && action.type
1384
const actionDescription =
@@ -110,16 +181,30 @@ function assertReducerShape(reducers: ReducersMapObject) {
110181
* into a single state object, whose keys correspond to the keys of the passed
111182
* reducer functions.
112183
*
113-
* @param {Object} reducers An object whose values correspond to different
114-
* reducer functions that need to be combined into one. One handy way to obtain
115-
* it is to use ES6 `import * as reducers` syntax. The reducers may never return
116-
* undefined for any action. Instead, they should return their initial state
117-
* if the state passed to them was undefined, and the current state for any
118-
* unrecognized action.
184+
* @template S Combined state object type.
185+
*
186+
* @param reducers An object whose values correspond to different reducer
187+
* functions that need to be combined into one. One handy way to obtain it
188+
* is to use ES6 `import * as reducers` syntax. The reducers may never
189+
* return undefined for any action. Instead, they should return their
190+
* initial state if the state passed to them was undefined, and the current
191+
* state for any unrecognized action.
119192
*
120-
* @returns {Function} A reducer function that invokes every reducer inside the
121-
* passed object, and builds a state object with the same shape.
193+
* @returns A reducer function that invokes every reducer inside the passed
194+
* object, and builds a state object with the same shape.
122195
*/
196+
export function combineReducers<S>(
197+
reducers: ReducersMapObject<S, any>
198+
): Reducer<CombinedState<S>>
199+
export function combineReducers<S, A extends Action = AnyAction>(
200+
reducers: ReducersMapObject<S, A>
201+
): Reducer<CombinedState<S>, A>
202+
export function combineReducers<M extends ReducersMapObject<any, any>>(
203+
reducers: M
204+
): Reducer<
205+
CombinedState<StateFromReducersMapObject<M>>,
206+
ActionFromReducersMapObject<M>
207+
>
123208
export default function combineReducers(reducers: ReducersMapObject) {
124209
const reducerKeys = Object.keys(reducers)
125210
const finalReducers: ReducersMapObject = {}

0 commit comments

Comments
 (0)