UNPKG

@reduxjs/toolkit

Version:

The official, opinionated, batteries-included toolset for efficient Redux development

242 lines (211 loc) 6.62 kB
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 } }