Skip to content

Commit 22ee6c5

Browse files
committed
Add PreloadedState generic
1 parent f503238 commit 22ee6c5

11 files changed

+285
-191
lines changed

src/applyMiddleware.ts

+19-25
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import compose from './compose'
22
import { Middleware, MiddlewareAPI } from './types/middleware'
3-
import { AnyAction } from './types/actions'
4-
import { StoreEnhancer, Dispatch, PreloadedState } from './types/store'
5-
import { Reducer } from './types/reducers'
3+
import { StoreEnhancer, Dispatch } from './types/store'
64

75
/**
86
* Creates a store enhancer that applies middleware to the dispatch method
@@ -55,29 +53,25 @@ export default function applyMiddleware<Ext, S = any>(
5553
export default function applyMiddleware(
5654
...middlewares: Middleware[]
5755
): StoreEnhancer<any> {
58-
return createStore =>
59-
<S, A extends AnyAction>(
60-
reducer: Reducer<S, A>,
61-
preloadedState?: PreloadedState<S>
62-
) => {
63-
const store = createStore(reducer, preloadedState)
64-
let dispatch: Dispatch = () => {
65-
throw new Error(
66-
'Dispatching while constructing your middleware is not allowed. ' +
67-
'Other middleware would not be applied to this dispatch.'
68-
)
69-
}
56+
return createStore => (reducer, preloadedState) => {
57+
const store = createStore(reducer, preloadedState)
58+
let dispatch: Dispatch = () => {
59+
throw new Error(
60+
'Dispatching while constructing your middleware is not allowed. ' +
61+
'Other middleware would not be applied to this dispatch.'
62+
)
63+
}
7064

71-
const middlewareAPI: MiddlewareAPI = {
72-
getState: store.getState,
73-
dispatch: (action, ...args) => dispatch(action, ...args)
74-
}
75-
const chain = middlewares.map(middleware => middleware(middlewareAPI))
76-
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
65+
const middlewareAPI: MiddlewareAPI = {
66+
getState: store.getState,
67+
dispatch: (action, ...args) => dispatch(action, ...args)
68+
}
69+
const chain = middlewares.map(middleware => middleware(middlewareAPI))
70+
dispatch = compose<typeof dispatch>(...chain)(store.dispatch)
7771

78-
return {
79-
...store,
80-
dispatch
81-
}
72+
return {
73+
...store,
74+
dispatch
8275
}
76+
}
8377
}

src/combineReducers.ts

+19-21
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { AnyAction, Action } from './types/actions'
22
import {
33
ActionFromReducersMapObject,
4+
PreloadedStateShapeFromReducersMapObject,
45
Reducer,
5-
ReducersMapObject,
6-
StateFromReducersMapObject
6+
StateFromReducersMapObject,
7+
UnknownReducersMapObject
78
} from './types/reducers'
8-
import { CombinedState } from './types/store'
99

1010
import ActionTypes from './utils/actionTypes'
1111
import isPlainObject from './utils/isPlainObject'
@@ -14,7 +14,7 @@ import { kindOf } from './utils/kindOf'
1414

1515
function getUnexpectedStateShapeWarningMessage(
1616
inputState: object,
17-
reducers: ReducersMapObject,
17+
reducers: UnknownReducersMapObject,
1818
action: Action,
1919
unexpectedKeyCache: { [key: string]: true }
2020
) {
@@ -60,7 +60,7 @@ function getUnexpectedStateShapeWarningMessage(
6060
}
6161
}
6262

63-
function assertReducerShape(reducers: ReducersMapObject) {
63+
function assertReducerShape(reducers: UnknownReducersMapObject) {
6464
Object.keys(reducers).forEach(key => {
6565
const reducer = reducers[key]
6666
const initialState = reducer(undefined, { type: ActionTypes.INIT })
@@ -110,21 +110,15 @@ function assertReducerShape(reducers: ReducersMapObject) {
110110
* @returns A reducer function that invokes every reducer inside the passed
111111
* object, and builds a state object with the same shape.
112112
*/
113-
export default function combineReducers<S>(
114-
reducers: ReducersMapObject<S, any>
115-
): Reducer<CombinedState<S>>
116-
export default function combineReducers<S, A extends Action = AnyAction>(
117-
reducers: ReducersMapObject<S, A>
118-
): Reducer<CombinedState<S>, A>
119-
export default function combineReducers<M extends ReducersMapObject>(
113+
export default function combineReducers<M extends UnknownReducersMapObject>(
120114
reducers: M
121115
): Reducer<
122-
CombinedState<StateFromReducersMapObject<M>>,
123-
ActionFromReducersMapObject<M>
124-
>
125-
export default function combineReducers(reducers: ReducersMapObject) {
116+
StateFromReducersMapObject<M>,
117+
ActionFromReducersMapObject<M>,
118+
Partial<PreloadedStateShapeFromReducersMapObject<M>>
119+
> {
126120
const reducerKeys = Object.keys(reducers)
127-
const finalReducers: ReducersMapObject = {}
121+
const finalReducers: UnknownReducersMapObject = {}
128122
for (let i = 0; i < reducerKeys.length; i++) {
129123
const key = reducerKeys[i]
130124

@@ -155,7 +149,9 @@ export default function combineReducers(reducers: ReducersMapObject) {
155149
}
156150

157151
return function combination(
158-
state: StateFromReducersMapObject<typeof reducers> = {},
152+
state:
153+
| StateFromReducersMapObject<typeof reducers>
154+
| Partial<PreloadedStateShapeFromReducersMapObject<typeof reducers>> = {},
159155
action: AnyAction
160156
) {
161157
if (shapeAssertionError) {
@@ -175,11 +171,12 @@ export default function combineReducers(reducers: ReducersMapObject) {
175171
}
176172

177173
let hasChanged = false
178-
const nextState: StateFromReducersMapObject<typeof reducers> = {}
174+
const nextState: Partial<StateFromReducersMapObject<typeof reducers>> = {}
179175
for (let i = 0; i < finalReducerKeys.length; i++) {
180176
const key = finalReducerKeys[i]
181177
const reducer = finalReducers[key]
182178
const previousStateForKey = state[key]
179+
// @ts-ignore
183180
const nextStateForKey = reducer(previousStateForKey, action)
184181
if (typeof nextStateForKey === 'undefined') {
185182
const actionType = action && action.type
@@ -191,11 +188,12 @@ export default function combineReducers(reducers: ReducersMapObject) {
191188
`If you want this reducer to hold no value, you can return null instead of undefined.`
192189
)
193190
}
194-
nextState[key] = nextStateForKey
191+
// @ts-ignore
192+
nextState[key as keyof typeof nextState] = nextStateForKey
195193
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
196194
}
197195
hasChanged =
198196
hasChanged || finalReducerKeys.length !== Object.keys(state).length
199-
return hasChanged ? nextState : state
197+
return (hasChanged ? nextState : state) as StateFromReducersMapObject<M>
200198
}
201199
}

src/createStore.ts

+21-16
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import $$observable from './utils/symbol-observable'
22

33
import {
44
Store,
5-
PreloadedState,
65
StoreEnhancer,
76
Dispatch,
87
Observer,
@@ -77,20 +76,22 @@ export function createStore<
7776
S,
7877
A extends Action,
7978
Ext extends {} = {},
80-
StateExt extends {} = {}
79+
StateExt extends {} = {},
80+
PreloadedState = S
8181
>(
82-
reducer: Reducer<S, A>,
83-
preloadedState?: PreloadedState<S>,
82+
reducer: Reducer<S, A, PreloadedState>,
83+
preloadedState?: PreloadedState | undefined,
8484
enhancer?: StoreEnhancer<Ext, StateExt>
8585
): Store<S, A, StateExt> & Ext
8686
export function createStore<
8787
S,
8888
A extends Action,
8989
Ext extends {} = {},
90-
StateExt extends {} = {}
90+
StateExt extends {} = {},
91+
PreloadedState = S
9192
>(
92-
reducer: Reducer<S, A>,
93-
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
93+
reducer: Reducer<S, A, PreloadedState>,
94+
preloadedState?: PreloadedState | StoreEnhancer<Ext, StateExt> | undefined,
9495
enhancer?: StoreEnhancer<Ext, StateExt>
9596
): Store<S, A, StateExt> & Ext {
9697
if (typeof reducer !== 'function') {
@@ -128,12 +129,14 @@ export function createStore<
128129

129130
return enhancer(createStore)(
130131
reducer,
131-
preloadedState as PreloadedState<S>
132-
) as Store<S, A, StateExt> & Ext
132+
preloadedState as PreloadedState | undefined
133+
)
133134
}
134135

135136
let currentReducer = reducer
136-
let currentState = preloadedState as S
137+
let currentState: S | PreloadedState | undefined = preloadedState as
138+
| PreloadedState
139+
| undefined
137140
let currentListeners: Map<number, ListenerCallback> | null = new Map()
138141
let nextListeners = currentListeners
139142
let listenerIdCounter = 0
@@ -315,7 +318,7 @@ export function createStore<
315318
)
316319
}
317320

318-
currentReducer = nextReducer
321+
currentReducer = nextReducer as unknown as Reducer<S, A, PreloadedState>
319322

320323
// This action has a similar effect to ActionTypes.INIT.
321324
// Any reducers that existed in both the new and old rootReducer
@@ -456,20 +459,22 @@ export function legacy_createStore<
456459
S,
457460
A extends Action,
458461
Ext extends {} = {},
459-
StateExt extends {} = {}
462+
StateExt extends {} = {},
463+
PreloadedState = S
460464
>(
461-
reducer: Reducer<S, A>,
462-
preloadedState?: PreloadedState<S>,
465+
reducer: Reducer<S, A, PreloadedState>,
466+
preloadedState?: PreloadedState | undefined,
463467
enhancer?: StoreEnhancer<Ext, StateExt>
464468
): Store<S, A, StateExt> & Ext
465469
export function legacy_createStore<
466470
S,
467471
A extends Action,
468472
Ext extends {} = {},
469-
StateExt extends {} = {}
473+
StateExt extends {} = {},
474+
PreloadedState = S
470475
>(
471476
reducer: Reducer<S, A>,
472-
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
477+
preloadedState?: PreloadedState | StoreEnhancer<Ext, StateExt> | undefined,
473478
enhancer?: StoreEnhancer<Ext, StateExt>
474479
): Store<S, A, StateExt> & Ext {
475480
return createStore(reducer, preloadedState as any, enhancer)

src/index.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
99
// types
1010
// store
1111
export {
12-
CombinedState,
13-
PreloadedState,
1412
Dispatch,
1513
Unsubscribe,
1614
Observable,
@@ -23,11 +21,13 @@ export {
2321
// reducers
2422
export {
2523
Reducer,
26-
ReducerFromReducersMapObject,
24+
UnknownReducersMapObject,
2725
ReducersMapObject,
2826
StateFromReducersMapObject,
27+
ReducerFromReducersMapObject,
2928
ActionFromReducer,
30-
ActionFromReducersMapObject
29+
ActionFromReducersMapObject,
30+
PreloadedStateShapeFromReducersMapObject
3131
} from './types/reducers'
3232
// action creators
3333
export { ActionCreator, ActionCreatorsMapObject } from './types/actions'

src/types/reducers.ts

+57-21
Original file line numberDiff line numberDiff line change
@@ -25,55 +25,91 @@ import { Action, AnyAction } from './actions'
2525
*
2626
* @template S The type of state consumed and produced by this reducer.
2727
* @template A The type of actions the reducer can potentially respond to.
28+
* @template PreloadedState The type of state consumed by this reducer the first time it's called.
2829
*/
29-
export type Reducer<S = any, A extends Action = AnyAction> = (
30-
state: S | undefined,
31-
action: A
32-
) => S
30+
export type Reducer<
31+
S = any,
32+
A extends Action = AnyAction,
33+
PreloadedState = S
34+
> = (state: S | PreloadedState | undefined, action: A) => S
35+
36+
/**
37+
* Any reducers map object should be assignable to this type.
38+
*/
39+
export type UnknownReducersMapObject = {
40+
[key: string]: (state: undefined, Action: AnyAction) => unknown
41+
}
3342

3443
/**
3544
* Object whose values correspond to different reducer functions.
3645
*
46+
* @template S The combined state of the reducers.
3747
* @template A The type of actions the reducers can potentially respond to.
48+
* @template PreloadedState The combined preloaded state of the reducers.
3849
*/
39-
export type ReducersMapObject<S = any, A extends Action = AnyAction> = {
40-
[K in keyof S]: Reducer<S[K], A>
41-
}
50+
export type ReducersMapObject<
51+
S = any,
52+
A extends Action = AnyAction,
53+
PreloadedState = S
54+
> = keyof PreloadedState extends keyof S
55+
? {
56+
[K in keyof S]: Reducer<
57+
S[K],
58+
A,
59+
K extends keyof PreloadedState ? PreloadedState[K] : never
60+
>
61+
}
62+
: never
4263

4364
/**
4465
* Infer a combined state shape from a `ReducersMapObject`.
4566
*
4667
* @template M Object map of reducers as provided to `combineReducers(map: M)`.
4768
*/
48-
export type StateFromReducersMapObject<M> = M extends ReducersMapObject
49-
? { [P in keyof M]: M[P] extends Reducer<infer S, any> ? S : never }
50-
: never
69+
export type StateFromReducersMapObject<M extends UnknownReducersMapObject> = {
70+
[P in keyof M]: M[P] extends Reducer<infer S> ? S : never
71+
}
5172

5273
/**
5374
* Infer reducer union type from a `ReducersMapObject`.
5475
*
5576
* @template M Object map of reducers as provided to `combineReducers(map: M)`.
5677
*/
57-
export type ReducerFromReducersMapObject<M> = M extends {
58-
[P in keyof M]: infer R
59-
}
60-
? R extends Reducer<any, any>
61-
? R
62-
: never
63-
: never
78+
export type ReducerFromReducersMapObject<M extends UnknownReducersMapObject> =
79+
M[keyof M]
6480

6581
/**
6682
* Infer action type from a reducer function.
6783
*
6884
* @template R Type of reducer.
6985
*/
70-
export type ActionFromReducer<R> = R extends Reducer<any, infer A> ? A : never
86+
export type ActionFromReducer<R extends Reducer> = R extends Reducer<
87+
any,
88+
infer A
89+
>
90+
? A
91+
: never
7192

7293
/**
7394
* Infer action union type from a `ReducersMapObject`.
7495
*
7596
* @template M Object map of reducers as provided to `combineReducers(map: M)`.
7697
*/
77-
export type ActionFromReducersMapObject<M> = M extends ReducersMapObject
78-
? ActionFromReducer<ReducerFromReducersMapObject<M>>
79-
: never
98+
export type ActionFromReducersMapObject<M extends UnknownReducersMapObject> =
99+
ActionFromReducer<ReducerFromReducersMapObject<M>>
100+
101+
/**
102+
* Infer a combined preloaded state shape from a `ReducersMapObject`.
103+
*
104+
* @template M Object map of reducers as provided to `combineReducers(map: M)`.
105+
*/
106+
export type PreloadedStateShapeFromReducersMapObject<
107+
M extends UnknownReducersMapObject
108+
> = {
109+
[P in keyof M]: M[P] extends (
110+
inputState: infer InputState,
111+
action: AnyAction
112+
) => any
113+
? InputState
114+
: never
115+
}

0 commit comments

Comments
 (0)