Reputation: 6467
I've attempted to create a kind of pattern-matching in typescript, particularly here for creating Redux reducers.
I'd really like to be able to specify ahead of time that a reducer must handle all actions of a particular type. I've tried to do that by creating an object which is keyed by action type, with values which are reducers. The typings for this mapping looks like:
export interface IReduxAction<T> {
type: T;
}
interface IReducer<S, A> {
(state: S, action: A): S;
}
export type IActionReducerMapping<S, A extends IReduxAction<string>> = Record<A['type'], IReducer<S, A>>;
This does most of what I was planning; any mapping created for a given action type must implement all of the types of action. The problem is that, with how I've set up the typings, the reducers in the mapping aren't able to infer the exact type of their action. For instance:
interface IUser {
id: number;
name: string;
age: number;
}
interface IUserState {
[id: number]: IUser;
}
interface IAddUserAction {
type: 'ADD_USER';
payload: IUser;
}
interface ISetUserNameAction {
type: 'SET_USER_NAME';
payload: {
id: IUser['id'];
name: IUser['name'];
}
}
type UserAction = IAddUserAction | ISetUserNameAction;
const mapping: IActionReducerMapping<IUserState, UserAction> = {
// here action is only aware of the properties on UserAction
// ideally it'd know that it has access to the properties of
// IAddUserAction
'ADD_USER': (state, action) => ({
...state,
[action.payload.id]: action.payload,
}),
'SET_USER_NAME': (state, action) => ({
...state,
[action.payload.id]: {
...state[action.payload.id],
name: action.payload.name,
}
}),
};
The problem is that the action in each reducer is only the union type UserAction, and so age, for instance, is not accessible. Is there a way to setup typings with this sort of pattern such that:
Upvotes: 0
Views: 202
Reputation: 249636
You can solve the problem with the action
not having the correct type by using a custom mapped type that maps over all the types (A['type']
) and uses the Extract
conditional type to get the correct action type for each type name.
export type IActionReducerMapping<S, A extends IReduxAction<string>> = {
[K in A['type']]: IReducer<S, Extract<A, { type: K }>>
};
Upvotes: 1