@reduxjs/toolkit
Version:
The official, opinionated, batteries-included toolset for efficient Redux development
249 lines (221 loc) • 7.73 kB
text/typescript
import type {
Reducer,
ReducersMapObject,
Middleware,
Action,
StoreEnhancer,
Store,
UnknownAction,
} from 'redux'
import {
applyMiddleware,
createStore,
compose,
combineReducers,
isPlainObject,
} from 'redux'
import type { DevToolsEnhancerOptions as DevToolsOptions } from './devtoolsExtension'
import { composeWithDevTools } from './devtoolsExtension'
import type {
ThunkMiddlewareFor,
GetDefaultMiddleware,
} from './getDefaultMiddleware'
import { buildGetDefaultMiddleware } from './getDefaultMiddleware'
import type {
ExtractDispatchExtensions,
ExtractStoreExtensions,
ExtractStateExtensions,
UnknownIfNonSpecific,
} from './tsHelpers'
import type { Tuple } from './utils'
import type { GetDefaultEnhancers } from './getDefaultEnhancers'
import { buildGetDefaultEnhancers } from './getDefaultEnhancers'
/**
* Options for `configureStore()`.
*
* @public
*/
export interface ConfigureStoreOptions<
S = any,
A extends Action = UnknownAction,
M extends Tuple<Middlewares<S>> = Tuple<Middlewares<S>>,
E extends Tuple<Enhancers> = Tuple<Enhancers>,
P = S,
> {
/**
* A single reducer function that will be used as the root reducer, or an
* object of slice reducers that will be passed to `combineReducers()`.
*/
reducer: Reducer<S, A, P> | ReducersMapObject<S, A, P>
/**
* An array of Redux middleware to install, or a callback receiving `getDefaultMiddleware` and returning a Tuple of middleware.
* If not supplied, defaults to the set of middleware returned by `getDefaultMiddleware()`.
*
* @example `middleware: (gDM) => gDM().concat(logger, apiMiddleware, yourCustomMiddleware)`
* @see https://redux-toolkit.js.org/api/getDefaultMiddleware#intended-usage
*/
middleware?: (getDefaultMiddleware: GetDefaultMiddleware<S>) => M
/**
* Whether to enable Redux DevTools integration. Defaults to `true`.
*
* Additional configuration can be done by passing Redux DevTools options
*/
devTools?: boolean | DevToolsOptions
/**
* Whether to check for duplicate middleware instances. Defaults to `true`.
*/
duplicateMiddlewareCheck?: boolean
/**
* The initial state, same as Redux's createStore.
* You may optionally specify it to hydrate the state
* from the server in universal apps, or to restore a previously serialized
* user session. If you use `combineReducers()` to produce the root reducer
* function (either directly or indirectly by passing an object as `reducer`),
* this must be an object with the same shape as the reducer map keys.
*/
// we infer here, and instead complain if the reducer doesn't match
preloadedState?: P
/**
* The store enhancers to apply. See Redux's `createStore()`.
* All enhancers will be included before the DevTools Extension enhancer.
* If you need to customize the order of enhancers, supply a callback
* function that will receive a `getDefaultEnhancers` function that returns a Tuple,
* and should return a Tuple of enhancers (such as `getDefaultEnhancers().concat(offline)`).
* If you only need to add middleware, you can use the `middleware` parameter instead.
*/
enhancers?: (getDefaultEnhancers: GetDefaultEnhancers<M>) => E
}
export type Middlewares<S> = ReadonlyArray<Middleware<{}, S>>
type Enhancers = ReadonlyArray<StoreEnhancer>
/**
* A Redux store returned by `configureStore()`. Supports dispatching
* side-effectful _thunks_ in addition to plain actions.
*
* @public
*/
export type EnhancedStore<
S = any,
A extends Action = UnknownAction,
E extends Enhancers = Enhancers,
> = ExtractStoreExtensions<E> &
Store<S, A, UnknownIfNonSpecific<ExtractStateExtensions<E>>>
/**
* A friendly abstraction over the standard Redux `createStore()` function.
*
* @param options The store configuration.
* @returns A configured Redux store.
*
* @public
*/
export function configureStore<
S = any,
A extends Action = UnknownAction,
M extends Tuple<Middlewares<S>> = Tuple<[ThunkMiddlewareFor<S>]>,
E extends Tuple<Enhancers> = Tuple<
[StoreEnhancer<{ dispatch: ExtractDispatchExtensions<M> }>, StoreEnhancer]
>,
P = S,
>(options: ConfigureStoreOptions<S, A, M, E, P>): EnhancedStore<S, A, E> {
const getDefaultMiddleware = buildGetDefaultMiddleware<S>()
const {
reducer = undefined,
middleware,
devTools = true,
duplicateMiddlewareCheck = true,
preloadedState = undefined,
enhancers = undefined,
} = options || {}
let rootReducer: Reducer<S, A, P>
if (typeof reducer === 'function') {
rootReducer = reducer
} else if (isPlainObject(reducer)) {
rootReducer = combineReducers(reducer) as unknown as Reducer<S, A, P>
} else {
throw new Error(
'`reducer` is a required argument, and must be a function or an object of functions that can be passed to combineReducers',
)
}
if (
process.env.NODE_ENV !== 'production' &&
middleware &&
typeof middleware !== 'function'
) {
throw new Error('`middleware` field must be a callback')
}
let finalMiddleware: Tuple<Middlewares<S>>
if (typeof middleware === 'function') {
finalMiddleware = middleware(getDefaultMiddleware)
if (
process.env.NODE_ENV !== 'production' &&
!Array.isArray(finalMiddleware)
) {
throw new Error(
'when using a middleware builder function, an array of middleware must be returned',
)
}
} else {
finalMiddleware = getDefaultMiddleware()
}
if (
process.env.NODE_ENV !== 'production' &&
finalMiddleware.some((item: any) => typeof item !== 'function')
) {
throw new Error(
'each middleware provided to configureStore must be a function',
)
}
if (process.env.NODE_ENV !== 'production' && duplicateMiddlewareCheck) {
let middlewareReferences = new Set<Middleware<any, S>>()
finalMiddleware.forEach((middleware) => {
if (middlewareReferences.has(middleware)) {
throw new Error(
'Duplicate middleware references found when creating the store. Ensure that each middleware is only included once.',
)
}
middlewareReferences.add(middleware)
})
}
let finalCompose = compose
if (devTools) {
finalCompose = composeWithDevTools({
// Enable capture of stack traces for dispatched Redux actions
trace: process.env.NODE_ENV !== 'production',
...(typeof devTools === 'object' && devTools),
})
}
const middlewareEnhancer = applyMiddleware(...finalMiddleware)
const getDefaultEnhancers = buildGetDefaultEnhancers<M>(middlewareEnhancer)
if (
process.env.NODE_ENV !== 'production' &&
enhancers &&
typeof enhancers !== 'function'
) {
throw new Error('`enhancers` field must be a callback')
}
let storeEnhancers =
typeof enhancers === 'function'
? enhancers(getDefaultEnhancers)
: getDefaultEnhancers()
if (process.env.NODE_ENV !== 'production' && !Array.isArray(storeEnhancers)) {
throw new Error('`enhancers` callback must return an array')
}
if (
process.env.NODE_ENV !== 'production' &&
storeEnhancers.some((item: any) => typeof item !== 'function')
) {
throw new Error(
'each enhancer provided to configureStore must be a function',
)
}
if (
process.env.NODE_ENV !== 'production' &&
finalMiddleware.length &&
!storeEnhancers.includes(middlewareEnhancer)
) {
console.error(
'middlewares were provided, but middleware enhancer was not included in final enhancers - make sure to call `getDefaultEnhancers`',
)
}
const composedEnhancer: StoreEnhancer<any> = finalCompose(...storeEnhancers)
return createStore(rootReducer, preloadedState as P, composedEnhancer)
}