Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How does keyof keyword works in the specific example

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

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

{
  [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}.

Playground link to code

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading