UNPKG

6.38 kBPlain TextView Raw
1import type {
2 Reducer,
3 ReducersMapObject,
4 Middleware,
5 Action,
6 AnyAction,
7 StoreEnhancer,
8 Store,
9 Dispatch,
10 PreloadedState,
11 CombinedState,
12} from 'redux'
13import { createStore, compose, applyMiddleware, combineReducers } from 'redux'
14import type { EnhancerOptions as DevToolsOptions } from './devtoolsExtension'
15import { composeWithDevTools } from './devtoolsExtension'
16
17import isPlainObject from './isPlainObject'
18import type {
19 ThunkMiddlewareFor,
20 CurriedGetDefaultMiddleware,
21} from './getDefaultMiddleware'
22import { curryGetDefaultMiddleware } from './getDefaultMiddleware'
23import type { DispatchForMiddlewares, NoInfer } from './tsHelpers'
24
25const IS_PRODUCTION = process.env.NODE_ENV === 'production'
26
27/**
28 * Callback function type, to be used in `ConfigureStoreOptions.enhancers`
29 *
30 * @public
31 */
32export type ConfigureEnhancersCallback = (
33 defaultEnhancers: readonly StoreEnhancer[]
34) => StoreEnhancer[]
35
36/**
37 * Options for `configureStore()`.
38 *
39 * @public
40 */
41export interface ConfigureStoreOptions<
42 S = any,
43 A extends Action = AnyAction,
44 M extends Middlewares<S> = Middlewares<S>
45> {
46 /**
47 * A single reducer function that will be used as the root reducer, or an
48 * object of slice reducers that will be passed to `combineReducers()`.
49 */
50 reducer: Reducer<S, A> | ReducersMapObject<S, A>
51
52 /**
53 * An array of Redux middleware to install. If not supplied, defaults to
54 * the set of middleware returned by `getDefaultMiddleware()`.
55 */
56 middleware?: ((getDefaultMiddleware: CurriedGetDefaultMiddleware<S>) => M) | M
57
58 /**
59 * Whether to enable Redux DevTools integration. Defaults to `true`.
60 *
61 * Additional configuration can be done by passing Redux DevTools options
62 */
63 devTools?: boolean | DevToolsOptions
64
65 /**
66 * The initial state, same as Redux's createStore.
67 * You may optionally specify it to hydrate the state
68 * from the server in universal apps, or to restore a previously serialized
69 * user session. If you use `combineReducers()` to produce the root reducer
70 * function (either directly or indirectly by passing an object as `reducer`),
71 * this must be an object with the same shape as the reducer map keys.
72 */
73 /*
74 Not 100% correct but the best approximation we can get:
75 - if S is a `CombinedState` applying a second `CombinedState` on it does not change anything.
76 - if it is not, there could be two cases:
77 - `ReducersMapObject<S, A>` is being passed in. In this case, we will call `combineReducers` on it and `CombinedState<S>` is correct
78 - `Reducer<S, A>` is being passed in. In this case, actually `CombinedState<S>` is wrong and `S` would be correct.
79 As we cannot distinguish between those two cases without adding another generic paramter,
80 we just make the pragmatic assumption that the latter almost never happens.
81 */
82 preloadedState?: PreloadedState<CombinedState<NoInfer<S>>>
83
84 /**
85 * The store enhancers to apply. See Redux's `createStore()`.
86 * All enhancers will be included before the DevTools Extension enhancer.
87 * If you need to customize the order of enhancers, supply a callback
88 * function that will receive the original array (ie, `[applyMiddleware]`),
89 * and should return a new array (such as `[applyMiddleware, offline]`).
90 * If you only need to add middleware, you can use the `middleware` parameter instead.
91 */
92 enhancers?: StoreEnhancer[] | ConfigureEnhancersCallback
93}
94
95type Middlewares<S> = ReadonlyArray<Middleware<{}, S>>
96
97/**
98 * A Redux store returned by `configureStore()`. Supports dispatching
99 * side-effectful _thunks_ in addition to plain actions.
100 *
101 * @public
102 */
103export interface EnhancedStore<
104 S = any,
105 A extends Action = AnyAction,
106 M extends Middlewares<S> = Middlewares<S>
107> extends Store<S, A> {
108 /**
109 * The `dispatch` method of your store, enhanced by all its middlewares.
110 *
111 * @inheritdoc
112 */
113 dispatch: DispatchForMiddlewares<M> & Dispatch<A>
114}
115
116/**
117 * A friendly abstraction over the standard Redux `createStore()` function.
118 *
119 * @param config The store configuration.
120 * @returns A configured Redux store.
121 *
122 * @public
123 */
124export function configureStore<
125 S = any,
126 A extends Action = AnyAction,
127 M extends Middlewares<S> = [ThunkMiddlewareFor<S>]
128>(options: ConfigureStoreOptions<S, A, M>): EnhancedStore<S, A, M> {
129 const curriedGetDefaultMiddleware = curryGetDefaultMiddleware<S>()
130
131 const {
132 reducer = undefined,
133 middleware = curriedGetDefaultMiddleware(),
134 devTools = true,
135 preloadedState = undefined,
136 enhancers = undefined,
137 } = options || {}
138
139 let rootReducer: Reducer<S, A>
140
141 if (typeof reducer === 'function') {
142 rootReducer = reducer
143 } else if (isPlainObject(reducer)) {
144 rootReducer = combineReducers(reducer)
145 } else {
146 throw new Error(
147 '"reducer" is a required argument, and must be a function or an object of functions that can be passed to combineReducers'
148 )
149 }
150
151 let finalMiddleware = middleware
152 if (typeof finalMiddleware === 'function') {
153 finalMiddleware = finalMiddleware(curriedGetDefaultMiddleware)
154
155 if (!IS_PRODUCTION && !Array.isArray(finalMiddleware)) {
156 throw new Error(
157 'when using a middleware builder function, an array of middleware must be returned'
158 )
159 }
160 }
161 if (
162 !IS_PRODUCTION &&
163 finalMiddleware.some((item) => typeof item !== 'function')
164 ) {
165 throw new Error(
166 'each middleware provided to configureStore must be a function'
167 )
168 }
169
170 const middlewareEnhancer = applyMiddleware(...finalMiddleware)
171
172 let finalCompose = compose
173
174 if (devTools) {
175 finalCompose = composeWithDevTools({
176 // Enable capture of stack traces for dispatched Redux actions
177 trace: !IS_PRODUCTION,
178 ...(typeof devTools === 'object' && devTools),
179 })
180 }
181
182 let storeEnhancers: StoreEnhancer[] = [middlewareEnhancer]
183
184 if (Array.isArray(enhancers)) {
185 storeEnhancers = [middlewareEnhancer, ...enhancers]
186 } else if (typeof enhancers === 'function') {
187 storeEnhancers = enhancers(storeEnhancers)
188 }
189
190 const composedEnhancer = finalCompose(...storeEnhancers) as any
191
192 return createStore(rootReducer, preloadedState, composedEnhancer)
193}
194
\No newline at end of file