Reputation: 5071
I am trying to create a generic type which will prove typing hints for Vuex
mutation. I read this article Vuex + TypeScript and I get inspired to create something more generic. I came up with something like this:
export type MutationType<S, P, K extends keyof P> = Record<K, (state: S, payload: P[K]) => void>;
// S
export type DiceGameState = {
round: number;
score: number;
diceItems: DiceHistoryItem[];
};
// P
export interface DiceGameMutationPayloadMap {
INCREMENT_SCORE: number;
DECREMENT_ROUND: number;
ADD_DICE_HISTORY_ITEM: DiceHistoryItem;
}
// K
export enum DiceGameMutationsKeys {
INCREMENT_SCORE = 'INCREMENT_SCORE',
DECREMENT_ROUND = 'DECREMENT_ROUND',
ADD_DICE_HISTORY_ITEM = 'ADD_DICE_HISTORY_ITEM'
}
export type DiceStoreMutation = MutationType<
DiceGameState,
DiceGameMutationPayloadMap,
keyof typeof DiceGameMutationsKeys
>;
export const mutations: MutationTree<DiceGameState> & DiceStoreMutation = {
DECREMENT_ROUND: (state: DiceGameState, payload: number) => {},
INCREMENT_SCORE: (state: DiceGameState, payload: number) => {},
ADD_DICE_HISTORY_ITEM: (state: DiceGameState, payload: DiceHistoryItem) => {}
};
Where:
S
→ type of mutation stateP
→ map of mutation name and type of payload assigned to this mutationK
→ mutation nameIf I check details of this MutationType
in my IDE I am getting this hint:
So the key in this object is one of K
keys and the value is a method which takes state which is type of S
and payload which is type of assigned value to P
.
If I try to compile this code I am getting this error:
Error:(7, 3) TS2322: Type '(state: DiceGameState, payload: number) => void' is not assignable to type '(state: DiceGameState, payload: number | DiceHistoryItem) => void'. Types of parameters 'payload' and 'payload' are incompatible. Type 'number | DiceHistoryItem' is not assignable to type 'number'. Type 'DiceHistoryItem' is not assignable to type 'number'.
Do you know how can I make my generic to work ?
I want to let TS infer the type of payload for a given mutation name. For example if you have a mutation named INCREMENT_SCORE
then the type of payload
hinted by TS should be a number. This is the reason why I created this: DiceGameMutationPayloadMap
, then after creating this const mutations
. Typescript should tell me what type payload should have (basing on name of mutation).
A good example of this can be also typing for addEventListenr
method provided by TypeScript. This type infers the type of event (ev
) basing on a given event name(type
):
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLButtonElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
For more details about this type you should check lib.dom.ts → line 6385
Upvotes: 5
Views: 1166
Reputation: 5071
Actually, it is possible. It takes me some time to figure out what I am doing wrong but in the end I understood my mistake.
My first mistake was inside DiceStoreMutation
because my 3rd argument of this generic referred to type of the key no key name. The proof of this we can see on attached screenshot:
We can see that Expanded shows that p
is actually a string, but it should be a union of string Literals 'ADD_DICE_HISTORY_ITEM' | 'DECREMENT_ROUND' | 'INCREMENT_SCORE'
. You could think that maybe I just should use keyof DiceGameMutationsKeys
instead of keyof typeof DiceGameMutationsKeys
which will refer to keys names of this interface instead of types of keys, but this of course occurred an error as expected. It made me think that I should change my generic MutationType
somehow. After I read @Joel Bourbonnais answers and check this very good example about the P[K]
part being a Index types
. I came up with a new idea for my MutationType
: Mapped generics
+ Index types
.
This new type came up with this shape:
export type MutationType<T, U> = {
[K in keyof U]: (state: T, payload: U[K]) => void;
};
And I can say only one thing It works a treat. Now I do not even need DiceGameMutationsKeys
because DiceGameMutationPayloadMap
provides all the information. You may ask why? I'm already rushing with an explanation:
K in keyof U
so K
can be only a key of U
so it will be a union of sting literals of U
keys interface.U[K]
so it will be a lookup for a type of U
under the K
key(one of this union of sting literals of U
keys interface)Below you can find a screenshot with proof:
Upvotes: 3