Skip to content

Commit

Permalink
createSlice: use template literal types for action type
Browse files Browse the repository at this point in the history
  • Loading branch information
phryneas committed Jul 8, 2022
1 parent 8ec8c39 commit 1324493
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 17 deletions.
36 changes: 26 additions & 10 deletions packages/toolkit/src/createSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export interface Slice<
* Action creators for the types of actions that are handled by the slice
* reducer.
*/
actions: CaseReducerActions<CaseReducers>
actions: CaseReducerActions<CaseReducers, Name>

/**
* The individual case reducer functions that were passed in the `reducers` parameter.
Expand Down Expand Up @@ -165,38 +165,54 @@ export type SliceCaseReducers<State> = {
| CaseReducerWithPrepare<State, PayloadAction<any, string, any, any>>
}

type SliceActionType<
SliceName extends string,
ActionName extends keyof any
> = ActionName extends string | number ? `${SliceName}/${ActionName}` : string

/**
* Derives the slice's `actions` property from the `reducers` options
*
* @public
*/
export type CaseReducerActions<CaseReducers extends SliceCaseReducers<any>> = {
export type CaseReducerActions<
CaseReducers extends SliceCaseReducers<any>,
SliceName extends string
> = {
[Type in keyof CaseReducers]: CaseReducers[Type] extends { prepare: any }
? ActionCreatorForCaseReducerWithPrepare<CaseReducers[Type]>
: ActionCreatorForCaseReducer<CaseReducers[Type]>
? ActionCreatorForCaseReducerWithPrepare<
CaseReducers[Type],
SliceActionType<SliceName, Type>
>
: ActionCreatorForCaseReducer<
CaseReducers[Type],
SliceActionType<SliceName, Type>
>
}

/**
* Get a `PayloadActionCreator` type for a passed `CaseReducerWithPrepare`
*
* @internal
*/
type ActionCreatorForCaseReducerWithPrepare<CR extends { prepare: any }> =
_ActionCreatorWithPreparedPayload<CR['prepare'], string>
type ActionCreatorForCaseReducerWithPrepare<
CR extends { prepare: any },
Type extends string
> = _ActionCreatorWithPreparedPayload<CR['prepare'], Type>

/**
* Get a `PayloadActionCreator` type for a passed `CaseReducer`
*
* @internal
*/
type ActionCreatorForCaseReducer<CR> = CR extends (
type ActionCreatorForCaseReducer<CR, Type extends string> = CR extends (
state: any,
action: infer Action
) => any
? Action extends { payload: infer P }
? PayloadActionCreator<P>
: ActionCreatorWithoutPayload
: ActionCreatorWithoutPayload
? PayloadActionCreator<P, Type>
: ActionCreatorWithoutPayload<Type>
: ActionCreatorWithoutPayload<Type>

/**
* Extracts the CaseReducers out of a `reducers` object, even if they are
Expand Down
17 changes: 10 additions & 7 deletions packages/toolkit/src/tests/createSlice.typetest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,13 @@ const value = actionCreators.anyKey
},
})

const s: string = counter.actions.increment.type
const t: string = counter.actions.decrement.type
const u: string = counter.actions.multiply.type
const s: 'counter/increment' = counter.actions.increment.type
const sa: 'counter/increment' = counter.actions.increment().type
const t: 'counter/decrement' = counter.actions.decrement.type
const ta: 'counter/decrement' = counter.actions.decrement().type
const u: 'counter/multiply' = counter.actions.multiply.type
const ua: 'counter/multiply' = counter.actions.multiply(1).type

// @ts-expect-error
const x: 'counter/increment' = counter.actions.increment.type
// @ts-expect-error
const y: 'increment' = counter.actions.increment.type
}
Expand Down Expand Up @@ -192,7 +193,9 @@ const value = actionCreators.anyKey
},
})

expectType<string>(counter.actions.incrementByStrLen('test').type)
expectType<'test/incrementByStrLen'>(
counter.actions.incrementByStrLen('test').type
)
expectType<number>(counter.actions.incrementByStrLen('test').payload)
expectType<string>(counter.actions.concatMetaStrLen('test').payload)
expectType<number>(counter.actions.concatMetaStrLen('test').meta)
Expand Down Expand Up @@ -384,7 +387,7 @@ const value = actionCreators.anyKey

const x: Action<unknown> = {} as any
if (mySlice.actions.setName.match(x)) {
expectType<string>(x.type)
expectType<'name/setName'>(x.type)
expectType<string>(x.payload)
} else {
// @ts-expect-error
Expand Down

0 comments on commit 1324493

Please sign in to comment.