UNPKG

6.57 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 { NoInfer, ExtractDispatchExtensions } 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 * @example `middleware: (gDM) => gDM().concat(logger, apiMiddleware, yourCustomMiddleware)`
57 * @see https://redux-toolkit.js.org/api/getDefaultMiddleware#intended-usage
58 */
59 middleware?: ((getDefaultMiddleware: CurriedGetDefaultMiddleware<S>) => M) | M
60
61 /**
62 * Whether to enable Redux DevTools integration. Defaults to `true`.
63 *
64 * Additional configuration can be done by passing Redux DevTools options
65 */
66 devTools?: boolean | DevToolsOptions
67
68 /**
69 * The initial state, same as Redux's createStore.
70 * You may optionally specify it to hydrate the state
71 * from the server in universal apps, or to restore a previously serialized
72 * user session. If you use `combineReducers()` to produce the root reducer
73 * function (either directly or indirectly by passing an object as `reducer`),
74 * this must be an object with the same shape as the reducer map keys.
75 */
76 /*
77 Not 100% correct but the best approximation we can get:
78 - if S is a `CombinedState` applying a second `CombinedState` on it does not change anything.
79 - if it is not, there could be two cases:
80 - `ReducersMapObject<S, A>` is being passed in. In this case, we will call `combineReducers` on it and `CombinedState<S>` is correct
81 - `Reducer<S, A>` is being passed in. In this case, actually `CombinedState<S>` is wrong and `S` would be correct.
82 As we cannot distinguish between those two cases without adding another generic paramter,
83 we just make the pragmatic assumption that the latter almost never happens.
84 */
85 preloadedState?: PreloadedState<CombinedState<NoInfer<S>>>
86
87 /**
88 * The store enhancers to apply. See Redux's `createStore()`.
89 * All enhancers will be included before the DevTools Extension enhancer.
90 * If you need to customize the order of enhancers, supply a callback
91 * function that will receive the original array (ie, `[applyMiddleware]`),
92 * and should return a new array (such as `[applyMiddleware, offline]`).
93 * If you only need to add middleware, you can use the `middleware` parameter instead.
94 */
95 enhancers?: StoreEnhancer[] | ConfigureEnhancersCallback
96}
97
98type Middlewares<S> = ReadonlyArray<Middleware<{}, S>>
99
100/**
101 * A Redux store returned by `configureStore()`. Supports dispatching
102 * side-effectful _thunks_ in addition to plain actions.
103 *
104 * @public
105 */
106export interface EnhancedStore<
107 S = any,
108 A extends Action = AnyAction,
109 M extends Middlewares<S> = Middlewares<S>
110> extends Store<S, A> {
111 /**
112 * The `dispatch` method of your store, enhanced by all its middlewares.
113 *
114 * @inheritdoc
115 */
116 dispatch: ExtractDispatchExtensions<M> & Dispatch<A>
117}
118
119/**
120 * A friendly abstraction over the standard Redux `createStore()` function.
121 *
122 * @param config The store configuration.
123 * @returns A configured Redux store.
124 *
125 * @public
126 */
127export function configureStore<
128 S = any,
129 A extends Action = AnyAction,
130 M extends Middlewares<S> = [ThunkMiddlewareFor<S>]
131>(options: ConfigureStoreOptions<S, A, M>): EnhancedStore<S, A, M> {
132 const curriedGetDefaultMiddleware = curryGetDefaultMiddleware<S>()
133
134 const {
135 reducer = undefined,
136 middleware = curriedGetDefaultMiddleware(),
137 devTools = true,
138 preloadedState = undefined,
139 enhancers = undefined,
140 } = options || {}
141
142 let rootReducer: Reducer<S, A>
143
144 if (typeof reducer === 'function') {
145 rootReducer = reducer
146 } else if (isPlainObject(reducer)) {
147 rootReducer = combineReducers(reducer)
148 } else {
149 throw new Error(
150 '"reducer" is a required argument, and must be a function or an object of functions that can be passed to combineReducers'
151 )
152 }
153
154 let finalMiddleware = middleware
155 if (typeof finalMiddleware === 'function') {
156 finalMiddleware = finalMiddleware(curriedGetDefaultMiddleware)
157
158 if (!IS_PRODUCTION && !Array.isArray(finalMiddleware)) {
159 throw new Error(
160 'when using a middleware builder function, an array of middleware must be returned'
161 )
162 }
163 }
164 if (
165 !IS_PRODUCTION &&
166 finalMiddleware.some((item: any) => typeof item !== 'function')
167 ) {
168 throw new Error(
169 'each middleware provided to configureStore must be a function'
170 )
171 }
172
173 const middlewareEnhancer = applyMiddleware(...finalMiddleware)
174
175 let finalCompose = compose
176
177 if (devTools) {
178 finalCompose = composeWithDevTools({
179 // Enable capture of stack traces for dispatched Redux actions
180 trace: !IS_PRODUCTION,
181 ...(typeof devTools === 'object' && devTools),
182 })
183 }
184
185 let storeEnhancers: StoreEnhancer[] = [middlewareEnhancer]
186
187 if (Array.isArray(enhancers)) {
188 storeEnhancers = [middlewareEnhancer, ...enhancers]
189 } else if (typeof enhancers === 'function') {
190 storeEnhancers = enhancers(storeEnhancers)
191 }
192
193 const composedEnhancer = finalCompose(...storeEnhancers) as any
194
195 return createStore(rootReducer, preloadedState, composedEnhancer)
196}
197
\No newline at end of file