-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: improve TS types for createReducer, composeReducers and composeM…
…iddleware
- Loading branch information
1 parent
75ca5e9
commit dcbe19f
Showing
5 changed files
with
326 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,32 @@ | ||
import { AnyAction, Reducer } from 'redux' | ||
|
||
type ReducerStateType<Reducer> = Reducer extends ( | ||
state: undefined | infer S0 extends {}, | ||
action: any | ||
) => infer S1 | ||
? S0 | S1 | ||
: Reducer extends (state: infer S0 extends {}, action: any) => infer S1 | ||
? S0 | S1 | ||
: never | ||
|
||
type ReducerActionType<Reducer> = Reducer extends ( | ||
state: any, | ||
action: infer A extends AnyAction | ||
) => any | ||
? A | ||
: never | ||
|
||
export function combineReducersWithActionHandlers< | ||
S = any, | ||
A extends AnyAction = AnyAction | ||
>(...reducers: Reducer<S, A>[]): Reducer<S, A> | ||
|
||
export default function composeReducers< | ||
S = any, | ||
A extends AnyAction = AnyAction | ||
>(...reducers: Reducer<S, A>[]): Reducer<S, A>[] | ||
Reducers extends Reducer<any, any>[] | ||
>(...reducers: Reducers): composeReducers<Reducers> | ||
|
||
type composeReducers<Reducers extends Reducer<any, any>[]> = Reducer< | ||
ReducerStateType<Reducers[number]>, | ||
ReducerActionType<Reducers[number]> | ||
> | ||
|
||
declare function composeReducers<Reducers extends Reducer<any, any>[]>( | ||
...reducers: Reducers | ||
): composeReducers<Reducers> | ||
|
||
export default composeReducers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,44 @@ | ||
import type { AnyAction, Reducer } from 'redux' | ||
|
||
type HandlersStateType< | ||
Handlers extends Record<string, (state: any, action: any) => any> | ||
> = ReducerStateType<Handlers[keyof Handlers]> | ||
|
||
export type ReducerStateType<Reducer> = Reducer extends ( | ||
state: infer S0, | ||
action: any | ||
) => infer S1 | ||
? Exclude<S0, undefined> | S1 | ||
: never | ||
|
||
type HandlersActionType< | ||
Handlers extends Record<string, (state: any, action: any) => any> | ||
> = ReducerActionType<Handlers[keyof Handlers]> | ||
|
||
export type ReducerActionType<Reducer> = Reducer extends ( | ||
state: any, | ||
action: infer A extends AnyAction | ||
) => any | ||
? A | ||
: never | ||
|
||
type AnyCoalesce<T, U> = 0 extends 1 & T ? U : T | ||
|
||
type ActionHandlers<S = any, A extends AnyAction = AnyAction> = { | ||
[T in A['type']]?: ( | ||
state: S | undefined, | ||
action: Extract<A, { type: T }> | ||
) => S | ||
} | ||
|
||
type InitializedActionHandlers<S = any, A extends AnyAction = AnyAction> = { | ||
[T in A['type']]?: (state: S, action: Extract<A, { type: T }>) => S | ||
} | ||
|
||
export default function createReducer<S = any, A extends AnyAction = AnyAction>( | ||
initialState: S, | ||
actionHandlers: Record<string, Reducer<S, A>> | ||
actionHandlers: InitializedActionHandlers<S, A> | ||
): Reducer<S, A> | ||
export default function createReducer<S = any, A extends AnyAction = AnyAction>( | ||
actionHandlers: Record<string, Reducer<S, A>> | ||
): Reducer<S, A> | ||
export default function createReducer<S = any, A extends AnyAction = AnyAction>( | ||
initialState: S, | ||
actionHandlers: Record<string, Reducer<S, A>> | ||
actionHandlers: ActionHandlers<S, A> | ||
): Reducer<S, A> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
import { Middleware, Reducer, applyMiddleware, createStore } from 'redux' | ||
import { composeMiddleware, composeReducers, createReducer } from '../src' | ||
|
||
export const LOGIN = 'login' | ||
export const LOGOUT = 'logout' | ||
|
||
export type LoginAction = { | ||
type: typeof LOGIN | ||
payload: { | ||
password: string | ||
} | ||
} | ||
export type LogoutAction = { | ||
type: typeof LOGOUT | ||
error?: true | ||
payload?: { | ||
error?: string | ||
} | ||
} | ||
|
||
export type AuthAction = LoginAction | LogoutAction | ||
|
||
export type Auth = { | ||
error: string | undefined | ||
} | ||
|
||
export const reducer1: Reducer<Auth, AuthAction> = createReducer< | ||
Auth, | ||
AuthAction | ||
>( | ||
{ error: undefined }, | ||
{ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
[LOGIN]: (state, action) => ({ | ||
error: undefined, | ||
}), | ||
[LOGOUT]: (state, { payload }) => ({ | ||
error: payload?.error, | ||
}), | ||
} | ||
) | ||
|
||
export const reducer2: Reducer<Auth, AuthAction> = createReducer< | ||
Auth, | ||
AuthAction | ||
>({ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
[LOGIN]: (state, action) => ({ | ||
error: undefined, | ||
}), | ||
[LOGOUT]: (state, { payload }) => ({ | ||
error: payload?.error, | ||
}), | ||
}) | ||
|
||
export const reducer3: Reducer<Auth, AuthAction> = createReducer< | ||
Auth, | ||
AuthAction | ||
>( | ||
{ error: undefined }, | ||
{ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
[LOGIN]: (state: Auth, action: LoginAction) => ({ | ||
error: undefined, | ||
}), | ||
[LOGOUT]: (state: Auth, { payload }: LogoutAction) => ({ | ||
error: payload?.error, | ||
}), | ||
} | ||
) | ||
|
||
export const reducer4: Reducer<Auth, AuthAction> = createReducer< | ||
Auth, | ||
AuthAction | ||
>({ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
[LOGIN]: (state: Auth | undefined, action: LoginAction) => ({ | ||
error: undefined, | ||
}), | ||
[LOGOUT]: (state: Auth | undefined, { payload }: LogoutAction) => ({ | ||
error: payload?.error, | ||
}), | ||
}) | ||
|
||
export const reducer5 = composeReducers( | ||
createReducer<Auth, LoginAction>({ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
[LOGIN]: (state: Auth | undefined, action: LoginAction) => ({ | ||
error: undefined, | ||
}), | ||
}), | ||
createReducer<Auth, LogoutAction>({ | ||
[LOGOUT]: (state: Auth | undefined, { payload }: LogoutAction) => ({ | ||
error: payload?.error, | ||
}), | ||
}) | ||
) | ||
|
||
export const reducer6: Reducer<Auth, AuthAction> = composeReducers( | ||
createReducer<Auth, LoginAction>({ | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
[LOGIN]: (state: Auth | undefined, action: LoginAction) => ({ | ||
error: undefined, | ||
}), | ||
}), | ||
createReducer<Auth, LogoutAction>({ | ||
[LOGOUT]: (state: Auth | undefined, { payload }: LogoutAction) => ({ | ||
error: payload?.error, | ||
}), | ||
}) | ||
) | ||
|
||
type AsyncAction<V> = { | ||
type: 'async' | ||
perform: () => Promise<V> | ||
} | ||
|
||
const asyncActionMiddleware: Middleware< | ||
{ | ||
<V>(action: AsyncAction<V>): Promise<V> | ||
}, | ||
Auth | ||
> = (store) => (next) => (action) => null as any | ||
|
||
type FooAction = { | ||
type: 'foo' | ||
} | ||
|
||
const fooActionMiddleware: Middleware< | ||
{ | ||
(action: FooAction): 'foo' | ||
}, | ||
Auth | ||
> = (store) => (next) => (action) => null as any | ||
|
||
const store = applyMiddleware( | ||
composeMiddleware(asyncActionMiddleware, fooActionMiddleware) | ||
)(createStore)(reducer1) | ||
|
||
const value1: 'foo' = store.dispatch({ type: 'foo' }) | ||
const value2: Promise<number> = store.dispatch({ | ||
type: 'async', | ||
perform: async () => 3, | ||
}) | ||
const value3: LoginAction = store.dispatch({ | ||
type: 'login', | ||
payload: { password: 'foo' }, | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters