@reduxjs/toolkit
Version:
The official, opinionated, batteries-included toolset for efficient Redux development
242 lines (211 loc) • 6.62 kB
text/typescript
import { Reducer } from 'redux'
import {
createAction,
PayloadAction,
PayloadActionCreator,
PrepareAction,
ActionCreatorWithoutPayload,
ActionCreatorWithPreparedPayload
} from './createAction'
import { createReducer, CaseReducers, CaseReducer } from './createReducer'
/**
* An action creator atttached to a slice.
*
* @deprecated please use PayloadActionCreator directly
*/
export type SliceActionCreator<P> = PayloadActionCreator<P>
export interface Slice<
State = any,
CaseReducers extends SliceCaseReducerDefinitions<State, PayloadActions> = {
[key: string]: any
}
> {
/**
* The slice name.
*/
name: string
/**
* The slice's reducer.
*/
reducer: Reducer<State>
/**
* Action creators for the types of actions that are handled by the slice
* reducer.
*/
actions: CaseReducerActions<CaseReducers>
caseReducers: SliceDefinedCaseReducers<CaseReducers, State>
}
/**
* Options for `createSlice()`.
*/
export interface CreateSliceOptions<
State = any,
CR extends SliceCaseReducerDefinitions<
State,
any
> = SliceCaseReducerDefinitions<State, any>
> {
/**
* The slice's name. Used to namespace the generated action types.
*/
name: string
/**
* The initial state to be returned by the slice reducer.
*/
initialState: State
/**
* A mapping from action types to action-type-specific *case reducer*
* functions. For every action type, a matching action creator will be
* generated using `createAction()`.
*/
reducers: CR
/**
* A mapping from action types to action-type-specific *case reducer*
* functions. These reducers should have existing action types used
* as the keys, and action creators will _not_ be generated.
*/
extraReducers?: CaseReducers<NoInfer<State>, any>
}
type PayloadActions<Types extends keyof any = string> = Record<
Types,
PayloadAction
>
type CaseReducerWithPrepare<State, Action extends PayloadAction> = {
reducer: CaseReducer<State, Action>
prepare: PrepareAction<Action['payload']>
}
type SliceCaseReducerDefinitions<State, PA extends PayloadActions> = {
[ActionType in keyof PA]:
| CaseReducer<State, PA[ActionType]>
| CaseReducerWithPrepare<State, PA[ActionType]>
}
type IfIsReducerFunctionWithoutAction<R, True, False = never> = R extends (
state: any
) => any
? True
: False
type IfIsCaseReducerWithPrepare<R, True, False = never> = R extends {
prepare: Function
}
? True
: False
type PayloadForReducer<R> = R extends (
state: any,
action: PayloadAction<infer P>
) => any
? P
: void
type PrepareActionForReducer<R> = R extends { prepare: infer Prepare }
? Prepare
: never
type ActionForReducer<R, S> = R extends (
state: S,
action: PayloadAction<infer P>
) => S
? PayloadAction<P>
: R extends {
reducer(state: any, action: PayloadAction<infer P>): any
}
? PayloadAction<P>
: unknown
type CaseReducerActions<
CaseReducers extends SliceCaseReducerDefinitions<any, any>
> = {
[Type in keyof CaseReducers]: IfIsCaseReducerWithPrepare<
CaseReducers[Type],
ActionCreatorWithPreparedPayload<
PrepareActionForReducer<CaseReducers[Type]>
>,
// else
IfIsReducerFunctionWithoutAction<
CaseReducers[Type],
ActionCreatorWithoutPayload,
// else
PayloadActionCreator<PayloadForReducer<CaseReducers[Type]>>
>
>
}
type SliceDefinedCaseReducers<
CaseReducers extends SliceCaseReducerDefinitions<any, any>,
State = any
> = {
[Type in keyof CaseReducers]: CaseReducer<
State,
ActionForReducer<CaseReducers[Type], State>
>
}
type NoInfer<T> = [T][T extends any ? 0 : never]
type SliceCaseReducersCheck<S, ACR> = {
[P in keyof ACR]: ACR[P] extends {
reducer(s: S, action?: { payload: infer O }): any
}
? {
prepare(...a: never[]): { payload: O }
}
: {}
}
type RestrictCaseReducerDefinitionsToMatchReducerAndPrepare<
S,
CR extends SliceCaseReducerDefinitions<S, any>
> = { reducers: SliceCaseReducersCheck<S, NoInfer<CR>> }
function getType(slice: string, actionKey: string): string {
return `${slice}/${actionKey}`
}
/**
* A function that accepts an initial state, an object full of reducer
* functions, and a "slice name", and automatically generates
* action creators and action types that correspond to the
* reducers and state.
*
* The `reducer` argument is passed to `createReducer()`.
*/
export function createSlice<
State,
CaseReducers extends SliceCaseReducerDefinitions<State, any>
>(
options: CreateSliceOptions<State, CaseReducers> &
RestrictCaseReducerDefinitionsToMatchReducerAndPrepare<State, CaseReducers>
): Slice<State, CaseReducers>
// internal definition is a little less restrictive
export function createSlice<
State,
CaseReducers extends SliceCaseReducerDefinitions<State, any>
>(
options: CreateSliceOptions<State, CaseReducers>
): Slice<State, CaseReducers> {
const { name, initialState } = options
if (!name) {
throw new Error('`name` is a required option for createSlice')
}
const reducers = options.reducers || {}
const extraReducers = options.extraReducers || {}
const reducerNames = Object.keys(reducers)
const sliceCaseReducersByName: Record<string, CaseReducer> = {}
const sliceCaseReducersByType: Record<string, CaseReducer> = {}
const actionCreators: Record<string, PayloadActionCreator> = {}
reducerNames.forEach(reducerName => {
const maybeReducerWithPrepare = reducers[reducerName]
const type = getType(name, reducerName)
let caseReducer: CaseReducer<State, any>
let prepareCallback: PrepareAction<any> | undefined
if (typeof maybeReducerWithPrepare === 'function') {
caseReducer = maybeReducerWithPrepare
} else {
caseReducer = maybeReducerWithPrepare.reducer
prepareCallback = maybeReducerWithPrepare.prepare
}
sliceCaseReducersByName[reducerName] = caseReducer
sliceCaseReducersByType[type] = caseReducer
actionCreators[reducerName] = prepareCallback
? createAction(type, prepareCallback)
: createAction(type)
})
const finalCaseReducers = { ...extraReducers, ...sliceCaseReducersByType }
const reducer = createReducer(initialState, finalCaseReducers as any)
return {
name,
reducer,
actions: actionCreators as any,
caseReducers: sliceCaseReducersByName as any
}
}