This question is inspired by an amazing answer found here:
My curiosity lies in why the indexing works in the mapped type trick.
Let's illustrate with 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;
// }
}
}
);
To clarify - I acknowledge that the first approach doesn't yield the expected results.
However, what puzzles me is why does the second approach does work.
If we delve deeper into this:
type NarrowDataPayload<T extends MyData> = { // Declaring an object
[K in keyof T]: { // For each key in T, a property is defined
key: K; // The values are dependent on T and K
fn: (data: T[K]) => void;
}
}[keyof T]; // Only retaining types accessible by keyof T.
The mystery is - how does TypeScript discern that the key type used here is "a" instead of "b."