I have the following type:
export type ActionsMap = {
add: Function;
remove: Function;
};
Then I have the following type that will map into actions that can be performed in a reducer in React:
export type Actions = {
[Key in keyof ActionsMap]: {
type: Key;
payload: ActionsMap[Key];
};
}[keyof ActionsMap];
I’m having issues understanding the [keyof ActionsMap]. Going each step I can see that
{
[Key in keyof ActionsMap]: {
type: Key;
payload: ActionsMap[Key];
};
}
Will loop over the keys in ActionsMap and transform this into a new type:
{
add: {
type: "add",
payload: Function
},
remove: {
type: "remove",
payload: Function
},
}
But how does exactly the last part ([keyof ActionsMap]) transforms the previous object into:
{ type: "add", payload: Function }
| { type: "remove", payload: Function }
>Solution :
If you have an object-like type T and a key-like type K that is constrained to keyof T, then T[K] is an indexed access type corresponding to the property values of T at keys of type K. Or, if you have a value t of type T and a value k of type K, then the expression t[k] has the type T[K]. If K is a union type like K1 | K2 | K3 this naturally means that T[K] will also be a union type like T[K1] | T[K2] | T[K3].
Using your types as examples, you can observe how this works:
type T = {
add: {
type: "add",
payload: Function
},
remove: {
type: "remove",
payload: Function
},
}
const t: T = {
add: { type: "add", payload: () => { } },
remove: { type: "remove", payload: () => { } }
}
type K = "add" | "remove"
const k: K = Math.random() < 0.5 ? "add" : "remove";
const tk: T[K] = t[k];
/* const tk: {
type: "add";
payload: Function;
} | {
type: "remove";
payload: Function;
} */
Since k might be either of the keys of t, then t[k] might be either of the properties of t. Therefore a union in K produces a union in T[K].
The specific technique you show, where you take a type function F<K> and produce a mapped type over it, into which you immediately index, like {[P in K]: F<P>}[K], called a distributive object type as coined in microsoft/TypeScript#47109, is one way to help distribute F<K> over unions in K. In your case, F<K> is {type: K, payload: ActionsMap[K]}, so the type Actions just distributes that over the union keyof ActionsMap to produce a new union {type: "add", payload: Function} | {type: "remove", payload: Function}.