0

This question is a follow up to this fantastic answer:

https://stackoverflow.com/a/64753409/1068446

What I'm wondering is why does the indexing in the mapped type trick work?

To give an example:

type MyData = {
  a: {
    alpha: string; 
  }; 
  b: {
    beta: number; 
  }
}

type NotNarrowPayload<T extends MyData> = {
  key: keyof T; 
  fn: (data: T[keyof T]) => void; 
}

type NarrowDataPayload<T extends MyData> = {
  [K in keyof T]: {
    key: K; 
    fn: (data: T[K]) => void; 
  }
}[keyof T]; 


function acceptsMTVTFunction<T extends MyData>(baseData: T,fn: NotNarrowPayload<T>) {

}


function acceptsMTVTFunction2<T extends MyData>(baseData: T,fn: NarrowDataPayload<T>) {

}



acceptsMTVTFunction(
    {
    a: {
      alpha: "a"
    }, 
    b: {
      beta: 99
    },
  }, 
  {
    key: "a", 
    fn: (data) => {
      // TypeScript doesn't match the key to the value type, and this makes sense. 
      // (parameter) data: {
      //     alpha: string;
      // } | {
      //     beta: number;
      // }
  }
}
); 



acceptsMTVTFunction2(
  {
    a: {
      alpha: "a"
    }, 
    b: {
      beta: 99
    },
  }, 
  {
    key: "a", 
    fn: (data) => {
      //       (parameter) data: {
      //     alpha: string;
      // }
  }
}
);

TypeScript Playground

Now, to be clear - I understand by the first approach doesn't work.

But what I'm not understanding is why the second approach does work.

If we take a closer look at this:

type NarrowDataPayload<T extends MyData> = { // We're declaring an object 
  [K in keyof T]: { // For each key in T, it will have a property
    key: K; // And these values will depend on T and K 
    fn: (data: T[K]) => void; 
  }
}[keyof T]; // But only keep the types accessible by keyof T. 

So the thing is - how does TypeScript know in this case that the key type we have used here is "a" and not "b".

dwjohnston
  • 11,163
  • 32
  • 99
  • 194

1 Answers1

0

TypeScript doesn't know how to distinguish the key type, look at how the NarrowDataPayload resolves:

const data = {
  a: {
    alpha: "a"
  },
  b: {
    beta: 99
  }
}; 


type A = NarrowDataPayload<typeof data>; 

// type A = {
//     key: "a";
//     fn: (data: {
//         alpha: string;
//     }) => void;
// } | {
//     key: "b";
//     fn: (data: {
//         beta: number;
//     }) => void;
// }

It's a union type.

What the index into the type mapping trick does is it keeps the key and fn aligned - and that's the improvement you're getting from the earlier approach.

dwjohnston
  • 11,163
  • 32
  • 99
  • 194