|
1 |
| -import { |
2 |
| - AnyAction, |
3 |
| - Action, |
4 |
| - ReducersMapObject, |
5 |
| - StateFromReducersMapObject |
6 |
| -} from '..' |
| 1 | +import { AnyAction, Action, Reducer } from '..' |
7 | 2 | import ActionTypes from './utils/actionTypes'
|
8 | 3 | import warning from './utils/warning'
|
9 | 4 | import isPlainObject from './utils/isPlainObject'
|
10 | 5 |
|
| 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 | + |
11 | 82 | function getUndefinedStateErrorMessage(key: string, action: Action) {
|
12 | 83 | const actionType = action && action.type
|
13 | 84 | const actionDescription =
|
@@ -110,16 +181,30 @@ function assertReducerShape(reducers: ReducersMapObject) {
|
110 | 181 | * into a single state object, whose keys correspond to the keys of the passed
|
111 | 182 | * reducer functions.
|
112 | 183 | *
|
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. |
119 | 192 | *
|
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. |
122 | 195 | */
|
| 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 | +> |
123 | 208 | export default function combineReducers(reducers: ReducersMapObject) {
|
124 | 209 | const reducerKeys = Object.keys(reducers)
|
125 | 210 | const finalReducers: ReducersMapObject = {}
|
|
0 commit comments