Skip to content

Commit 5a084a6

Browse files
committed
Fix type of next parameter in StoreEnhancer type
1 parent 29d5d88 commit 5a084a6

File tree

6 files changed

+118
-105
lines changed

6 files changed

+118
-105
lines changed

src/applyMiddleware.ts

+2-7
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
import compose from './compose'
22
import { Middleware, MiddlewareAPI } from './types/middleware'
33
import { AnyAction } from './types/actions'
4-
import {
5-
StoreEnhancer,
6-
Dispatch,
7-
PreloadedState,
8-
StoreEnhancerStoreCreator
9-
} from './types/store'
4+
import { StoreEnhancer, Dispatch, PreloadedState } from './types/store'
105
import { Reducer } from './types/reducers'
116

127
/**
@@ -60,7 +55,7 @@ export default function applyMiddleware<Ext, S = any>(
6055
export default function applyMiddleware(
6156
...middlewares: Middleware[]
6257
): StoreEnhancer<any> {
63-
return (createStore: StoreEnhancerStoreCreator) =>
58+
return createStore =>
6459
<S, A extends AnyAction>(
6560
reducer: Reducer<S, A>,
6661
preloadedState?: PreloadedState<S>

src/createStore.ts

+24-9
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,12 @@ import { kindOf } from './utils/kindOf'
3939
* `import { legacy_createStore as createStore} from 'redux'`
4040
*
4141
*/
42-
export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
42+
export function createStore<
43+
S,
44+
A extends Action,
45+
Ext extends {} = {},
46+
StateExt extends {} = {}
47+
>(
4348
reducer: Reducer<S, A>,
4449
enhancer?: StoreEnhancer<Ext, StateExt>
4550
): Store<S, A, StateExt> & Ext
@@ -68,12 +73,22 @@ export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
6873
* `import { legacy_createStore as createStore} from 'redux'`
6974
*
7075
*/
71-
export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
76+
export function createStore<
77+
S,
78+
A extends Action,
79+
Ext extends {} = {},
80+
StateExt extends {} = {}
81+
>(
7282
reducer: Reducer<S, A>,
7383
preloadedState?: PreloadedState<S>,
7484
enhancer?: StoreEnhancer<Ext, StateExt>
7585
): Store<S, A, StateExt> & Ext
76-
export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
86+
export function createStore<
87+
S,
88+
A extends Action,
89+
Ext extends {} = {},
90+
StateExt extends {} = {}
91+
>(
7792
reducer: Reducer<S, A>,
7893
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
7994
enhancer?: StoreEnhancer<Ext, StateExt>
@@ -401,8 +416,8 @@ export function createStore<S, A extends Action, Ext = {}, StateExt = never>(
401416
export function legacy_createStore<
402417
S,
403418
A extends Action,
404-
Ext = {},
405-
StateExt = never
419+
Ext extends {} = {},
420+
StateExt extends {} = {}
406421
>(
407422
reducer: Reducer<S, A>,
408423
enhancer?: StoreEnhancer<Ext, StateExt>
@@ -440,8 +455,8 @@ export function legacy_createStore<
440455
export function legacy_createStore<
441456
S,
442457
A extends Action,
443-
Ext = {},
444-
StateExt = never
458+
Ext extends {} = {},
459+
StateExt extends {} = {}
445460
>(
446461
reducer: Reducer<S, A>,
447462
preloadedState?: PreloadedState<S>,
@@ -450,8 +465,8 @@ export function legacy_createStore<
450465
export function legacy_createStore<
451466
S,
452467
A extends Action,
453-
Ext = {},
454-
StateExt = never
468+
Ext extends {} = {},
469+
StateExt extends {} = {}
455470
>(
456471
reducer: Reducer<S, A>,
457472
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,

src/index.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ export {
1818
Store,
1919
StoreCreator,
2020
StoreEnhancer,
21-
StoreEnhancerStoreCreator,
22-
ExtendState
21+
StoreEnhancerStoreCreator
2322
} from './types/store'
2423
// reducers
2524
export {

src/types/store.ts

+14-29
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,6 @@ import { Action, AnyAction } from './actions'
22
import { Reducer } from './reducers'
33
import '../utils/symbol-observable'
44

5-
/**
6-
* Extend the state
7-
*
8-
* This is used by store enhancers and store creators to extend state.
9-
* If there is no state extension, it just returns the state, as is, otherwise
10-
* it returns the state joined with its extension.
11-
*
12-
* Reference for future devs:
13-
* https://github.com/microsoft/TypeScript/issues/31751#issuecomment-498526919
14-
*/
15-
export type ExtendState<State, Extension> = [Extension] extends [never]
16-
? State
17-
: State & Extension
18-
195
/**
206
* Internal "virtual" symbol used to make the `CombinedState` type unique.
217
*/
@@ -134,11 +120,7 @@ export type Observer<T> = {
134120
* @template A the type of actions which may be dispatched by this store.
135121
* @template StateExt any extension to state from store enhancers
136122
*/
137-
export interface Store<
138-
S = any,
139-
A extends Action = AnyAction,
140-
StateExt = never
141-
> {
123+
export interface Store<S = any, A extends Action = AnyAction, StateExt = {}> {
142124
/**
143125
* Dispatches an action. It is the only way to trigger a state change.
144126
*
@@ -172,7 +154,7 @@ export interface Store<
172154
*
173155
* @returns The current state tree of your application.
174156
*/
175-
getState(): ExtendState<S, StateExt>
157+
getState(): S & StateExt
176158

177159
/**
178160
* Adds a change listener. It will be called any time an action is
@@ -217,7 +199,7 @@ export interface Store<
217199
* For more information, see the observable proposal:
218200
* https://github.com/tc39/proposal-observable
219201
*/
220-
[Symbol.observable](): Observable<ExtendState<S, StateExt>>
202+
[Symbol.observable](): Observable<S & StateExt>
221203
}
222204

223205
/**
@@ -232,11 +214,11 @@ export interface Store<
232214
* @template StateExt State extension that is mixed into the state type.
233215
*/
234216
export interface StoreCreator {
235-
<S, A extends Action, Ext = {}, StateExt = never>(
217+
<S, A extends Action, Ext extends {} = {}, StateExt extends {} = {}>(
236218
reducer: Reducer<S, A>,
237219
enhancer?: StoreEnhancer<Ext, StateExt>
238220
): Store<S, A, StateExt> & Ext
239-
<S, A extends Action, Ext = {}, StateExt = never>(
221+
<S, A extends Action, Ext extends {} = {}, StateExt extends {} = {}>(
240222
reducer: Reducer<S, A>,
241223
preloadedState?: PreloadedState<S>,
242224
enhancer?: StoreEnhancer<Ext>
@@ -264,13 +246,16 @@ export interface StoreCreator {
264246
* @template Ext Store extension that is mixed into the Store type.
265247
* @template StateExt State extension that is mixed into the state type.
266248
*/
267-
export type StoreEnhancer<Ext = {}, StateExt = never> = (
268-
next: StoreEnhancerStoreCreator<Ext, StateExt>
269-
) => StoreEnhancerStoreCreator<Ext, StateExt>
270-
export type StoreEnhancerStoreCreator<Ext = {}, StateExt = never> = <
271-
S = any,
272-
A extends Action = AnyAction
249+
export type StoreEnhancer<Ext extends {} = {}, StateExt extends {} = {}> = <
250+
NextExt extends {},
251+
NextStateExt extends {}
273252
>(
253+
next: StoreEnhancerStoreCreator<NextExt, NextStateExt>
254+
) => StoreEnhancerStoreCreator<NextExt & Ext, NextStateExt & StateExt>
255+
export type StoreEnhancerStoreCreator<
256+
Ext extends {} = {},
257+
StateExt extends {} = {}
258+
> = <S = any, A extends Action = AnyAction>(
274259
reducer: Reducer<S, A>,
275260
preloadedState?: PreloadedState<S>
276261
) => Store<S, A, StateExt> & Ext

test/typescript/enhancers.ts

+76-16
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,13 @@ function stateExtension() {
6464
reducer: Reducer<S, A>,
6565
preloadedState?: any
6666
) => {
67-
const wrappedReducer: Reducer<S & ExtraState, A> = (state, action) => {
68-
const newState = reducer(state, action)
69-
return {
70-
...newState,
71-
extraField: 'extra'
67+
function wrapReducer(reducer: Reducer<S, A>): Reducer<S & ExtraState, A> {
68+
return (state, action) => {
69+
const newState = reducer(state, action)
70+
return {
71+
...newState,
72+
extraField: 'extra'
73+
}
7274
}
7375
}
7476
const wrappedPreloadedState = preloadedState
@@ -77,7 +79,13 @@ function stateExtension() {
7779
extraField: 'extra'
7880
}
7981
: undefined
80-
return createStore(wrappedReducer, wrappedPreloadedState)
82+
const store = createStore(wrapReducer(reducer), wrappedPreloadedState)
83+
return {
84+
...store,
85+
replaceReducer(nextReducer: Reducer<S, A>) {
86+
store.replaceReducer(wrapReducer(nextReducer))
87+
}
88+
}
8189
}
8290

8391
const store = createStore(reducer, enhancer)
@@ -96,8 +104,10 @@ function extraMethods() {
96104
createStore =>
97105
(...args) => {
98106
const store = createStore(...args)
99-
store.method = () => 'foo'
100-
return store
107+
return {
108+
...store,
109+
method: () => 'foo'
110+
}
101111
}
102112

103113
const store = createStore(reducer, enhancer)
@@ -122,11 +132,13 @@ function replaceReducerExtender() {
122132
reducer: Reducer<S, A>,
123133
preloadedState?: any
124134
) => {
125-
const wrappedReducer: Reducer<S & ExtraState, A> = (state, action) => {
126-
const newState = reducer(state, action)
127-
return {
128-
...newState,
129-
extraField: 'extra'
135+
function wrapReducer(reducer: Reducer<S, A>): Reducer<S & ExtraState, A> {
136+
return (state, action) => {
137+
const newState = reducer(state, action)
138+
return {
139+
...newState,
140+
extraField: 'extra'
141+
}
130142
}
131143
}
132144
const wrappedPreloadedState = preloadedState
@@ -135,7 +147,14 @@ function replaceReducerExtender() {
135147
extraField: 'extra'
136148
}
137149
: undefined
138-
return createStore(wrappedReducer, wrappedPreloadedState)
150+
const store = createStore(wrapReducer(reducer), wrappedPreloadedState)
151+
return {
152+
...store,
153+
replaceReducer(nextReducer: Reducer<S, A>) {
154+
store.replaceReducer(wrapReducer(nextReducer))
155+
},
156+
method: () => 'foo'
157+
}
139158
}
140159

141160
const store = createStore<
@@ -270,14 +289,14 @@ function finalHelmersonExample() {
270289
<S, A extends Action<unknown>>(
271290
reducer: Reducer<S, A>,
272291
preloadedState?: any
273-
): Store<S, A, ExtraState> & { persistor: Store<S, A, ExtraState> } => {
292+
) => {
274293
const persistedReducer = persistReducer<S, A>(persistConfig, reducer)
275294
const store = createStore(persistedReducer, preloadedState)
276295
const persistor = persistStore(store)
277296

278297
return {
279298
...store,
280-
replaceReducer: nextReducer => {
299+
replaceReducer: (nextReducer: Reducer<S, A>) => {
281300
store.replaceReducer(persistReducer(persistConfig, nextReducer))
282301
},
283302
persistor
@@ -308,3 +327,44 @@ function finalHelmersonExample() {
308327
// @ts-expect-error
309328
store.getState().wrongField
310329
}
330+
331+
function composedEnhancers() {
332+
interface State {
333+
someState: string
334+
}
335+
const reducer: Reducer<State> = null as any
336+
337+
interface Ext1 {
338+
enhancer1: string
339+
}
340+
interface Ext2 {
341+
enhancer2: number
342+
}
343+
344+
const enhancer1: StoreEnhancer<Ext1> =
345+
createStore => (reducer, preloadedState) => {
346+
const store = createStore(reducer, preloadedState)
347+
return {
348+
...store,
349+
enhancer1: 'foo'
350+
}
351+
}
352+
353+
const enhancer2: StoreEnhancer<Ext2> =
354+
createStore => (reducer, preloadedState) => {
355+
const store = createStore(reducer, preloadedState)
356+
return {
357+
...store,
358+
enhancer2: 5
359+
}
360+
}
361+
362+
const composedEnhancer: StoreEnhancer<Ext1 & Ext2> = createStore =>
363+
enhancer2(enhancer1(createStore))
364+
365+
const enhancedStore = createStore(reducer, composedEnhancer)
366+
enhancedStore.enhancer1
367+
enhancedStore.enhancer2
368+
// @ts-expect-error
369+
enhancedStore.enhancer3
370+
}

test/typescript/store.ts

+1-42
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ import {
55
Action,
66
StoreEnhancer,
77
Unsubscribe,
8-
Observer,
9-
ExtendState
8+
Observer
109
} from '../../src'
1110
import 'symbol-observable'
1211

@@ -22,46 +21,6 @@ type State = {
2221
e: BrandedString
2322
}
2423

25-
/* extended state */
26-
const noExtend: ExtendState<State, never> = {
27-
a: 'a',
28-
b: {
29-
c: 'c',
30-
d: 'd'
31-
},
32-
e: brandedString
33-
}
34-
35-
const noExtendError: ExtendState<State, never> = {
36-
a: 'a',
37-
b: {
38-
c: 'c',
39-
d: 'd'
40-
},
41-
e: brandedString,
42-
// @ts-expect-error
43-
f: 'oops'
44-
}
45-
46-
const yesExtend: ExtendState<State, { yes: 'we can' }> = {
47-
a: 'a',
48-
b: {
49-
c: 'c',
50-
d: 'd'
51-
},
52-
e: brandedString,
53-
yes: 'we can'
54-
}
55-
// @ts-expect-error
56-
const yesExtendError: ExtendState<State, { yes: 'we can' }> = {
57-
a: 'a',
58-
b: {
59-
c: 'c',
60-
d: 'd'
61-
},
62-
e: brandedString
63-
}
64-
6524
interface DerivedAction extends Action {
6625
type: 'a'
6726
b: 'b'

0 commit comments

Comments
 (0)