-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add prepareAction option to createAction #149
Changes from all commits
0b3bad1
8ec7395
4f55df2
5ed1286
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,11 @@ | ||
import { Reducer } from 'redux' | ||
import { createAction, PayloadAction, PayloadActionCreator } from './createAction' | ||
import { createReducer, CaseReducers } from './createReducer' | ||
import { | ||
createAction, | ||
PayloadAction, | ||
PayloadActionCreator, | ||
PrepareAction | ||
} from './createAction' | ||
import { createReducer, CaseReducers, CaseReducer } from './createReducer' | ||
import { createSliceSelector, createSelectorName } from './sliceSelector' | ||
|
||
/** | ||
|
@@ -12,8 +17,8 @@ export type SliceActionCreator<P> = PayloadActionCreator<P> | |
|
||
export interface Slice< | ||
S = any, | ||
AP extends { [key: string]: any } = { [key: string]: any } | ||
> { | ||
AC extends { [key: string]: any } = { [key: string]: any } | ||
> { | ||
/** | ||
* The slice name. | ||
*/ | ||
|
@@ -28,7 +33,7 @@ export interface Slice< | |
* Action creators for the types of actions that are handled by the slice | ||
* reducer. | ||
*/ | ||
actions: { [type in keyof AP]: PayloadActionCreator<AP[type]> } | ||
actions: AC | ||
|
||
/** | ||
* Selectors for the slice reducer state. `createSlice()` inserts a single | ||
|
@@ -44,8 +49,8 @@ export interface Slice< | |
*/ | ||
export interface CreateSliceOptions< | ||
S = any, | ||
CR extends CaseReducers<S, any> = CaseReducers<S, any> | ||
> { | ||
CR extends SliceCaseReducers<S, any> = SliceCaseReducers<S, any> | ||
> { | ||
/** | ||
* The slice's name. Used to namespace the generated action types and to | ||
* name the selector for retrieving the reducer's state. | ||
|
@@ -72,12 +77,36 @@ export interface CreateSliceOptions< | |
extraReducers?: CaseReducers<S, any> | ||
} | ||
|
||
type CaseReducerActionPayloads<CR extends CaseReducers<any, any>> = { | ||
type PayloadActions<T extends keyof any = string> = Record<T, PayloadAction> | ||
|
||
type EnhancedCaseReducer<S, A extends PayloadAction> = { | ||
reducer: CaseReducer<S, A> | ||
prepare: PrepareAction<A['payload']> | ||
} | ||
|
||
type SliceCaseReducers<S, PA extends PayloadActions> = { | ||
[T in keyof PA]: CaseReducer<S, PA[T]> | EnhancedCaseReducer<S, PA[T]> | ||
} | ||
|
||
type CaseReducerActions<CR extends SliceCaseReducers<any, any>> = { | ||
[T in keyof CR]: CR[T] extends (state: any) => any | ||
? void | ||
: (CR[T] extends (state: any, action: PayloadAction<infer P>) => any | ||
? P | ||
: void) | ||
? PayloadActionCreator<void> | ||
: (CR[T] extends (state: any, action: PayloadAction<infer P>) => any | ||
? PayloadActionCreator<P> | ||
: CR[T] extends { prepare: PrepareAction<infer P> } | ||
? PayloadActionCreator<P, string, CR[T]['prepare']> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. wat There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, come on, THAT one isn't even so much different than before =D Big difference is that it builds the complete ActionCreator type instead of just the payload, and also it now has 4 cases instead of 3. |
||
: PayloadActionCreator<void>) | ||
} | ||
|
||
type NoInfer<T> = [T][T extends any ? 0 : never]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. voodoo There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I'm still "wat" here, too :) |
||
type SliceCaseReducersCheck<S, ACR> = { | ||
[P in keyof ACR] : ACR[P] extends { | ||
reducer(s:S, action?: { payload: infer O }): any | ||
} ? { | ||
prepare(...a:never[]): { payload: O } | ||
} : { | ||
|
||
} | ||
} | ||
|
||
function getType(slice: string, actionKey: string): string { | ||
|
@@ -92,25 +121,36 @@ function getType(slice: string, actionKey: string): string { | |
* | ||
* The `reducer` argument is passed to `createReducer()`. | ||
*/ | ||
export function createSlice<S, CR extends CaseReducers<S, any>>( | ||
export function createSlice<S, CR extends SliceCaseReducers<S, any>>( | ||
options: CreateSliceOptions<S, CR> & { reducers: SliceCaseReducersCheck<S, NoInfer<CR>> } | ||
): Slice<S, CaseReducerActions<CR>> | ||
export function createSlice<S, CR extends SliceCaseReducers<S, any>>( | ||
options: CreateSliceOptions<S, CR> | ||
): Slice<S, CaseReducerActionPayloads<CR>> { | ||
): Slice<S, CaseReducerActions<CR>> { | ||
const { slice = '', initialState } = options | ||
const reducers = options.reducers || {} | ||
const extraReducers = options.extraReducers || {} | ||
const actionKeys = Object.keys(reducers) | ||
|
||
const reducerMap = actionKeys.reduce((map, actionKey) => { | ||
map[getType(slice, actionKey)] = reducers[actionKey] | ||
let maybeEnhancedReducer = reducers[actionKey] | ||
map[getType(slice, actionKey)] = | ||
typeof maybeEnhancedReducer === 'function' | ||
? maybeEnhancedReducer | ||
: maybeEnhancedReducer.reducer | ||
return map | ||
}, extraReducers) | ||
|
||
const reducer = createReducer(initialState, reducerMap) | ||
|
||
const actionMap = actionKeys.reduce( | ||
(map, action) => { | ||
let maybeEnhancedReducer = reducers[action] | ||
const type = getType(slice, action) | ||
map[action] = createAction(type) | ||
map[action] = | ||
typeof maybeEnhancedReducer === 'function' | ||
? createAction(type) | ||
: createAction(type, maybeEnhancedReducer.prepare) | ||
return map | ||
}, | ||
{} as any | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just so you know, my eyes are still bleeding here :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that one is wild. But it defines many different behaviours :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW here's how I understand this code.
PayloadActionCreator always has a
type
property of typeT
If PA is a function we check if it has a
meta
field. If yes the final type isIf the return value does not have a
meta
field it'sNow the else, if PA is
not
a function we default back to the older behavior which terrifies me.@phryneas as some practical feedback, it might be helpful to make it clear to the reader
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost, but no.
All Action creators share the
{ type: T }
, but the rest of the functionality differs.If a
PA
argument is passed, the first branch will be taken.Within that branch, if PA returns something with
{ meta }
, again take the first branch. Resulting type is a function(...args: Parameters<PA>) => PayloadAction<P, T, M>
(with the additionaltype
from above - all that follow will, too). Otherwise it's a function(...args: Parameters<PA>) => PayloadAction<P, T>)
.Now, on to the other branch - no PA given. This has to handle some special cases first and then falls back to the most used case.
If P is undefined, it returns a method that might take 0 or 1 argument. If P is void, it's a method with 0 arguments. Otherwise it is a method with 1 argument. All these methods infer their argument and return that inferred argument as the payload of the returned action.
Formatting this is in a language that knows only ternaries and in a file that will be shuffled around with prettier is unfortunately something I did not achieve :/