varför ordet keyof i TypeScript beter sig annorlunda i två `type definitioner`?


varför kan res1 inte veta att dess typ är number som res2 gör?

interface P {
  name: string;
  age: number;
}
const people: P = {
  age: 30,
  name: 'Peter',
};

type T = keyof P
type Fn1 = (p: P, t: T) => P[T]
type Fn2 = <P, T extends keyof P>(p: P, t: T) => P[T]

const fn1: Fn1 = (p, t) => {
  return p[t]
}
const fn2: Fn2 = (p, t) => {
  return p[t]
}

const res1 = fn1(people, 'age');
const res2 = fn2(people, 'age');

Svar #1

Först och främst låt oss ändra definitionen av fn2 så att vi kan förstå vikten av typargumentet. Och vi förvirrar oss inte med T som är keyof P.

typ Fn2=<P, Tx utvidgar keyof P>(p: P, t: Tx) => P[Tx]

Den generiska versionen av en funktion har alltid generiska typargument som extra argument till den befintliga funktionen som hjälper kompileraren att lösa typer. fn1 har inga typargument, i detta fall kan inte kompileraren skilja mellan fn1(people, 'age') och fn1(people, 'name').

Att expandera fn1 vid kompileringstid, (p: P, key: "age" | "name") => p['age' | 'name'] finns det inget generiskt argument tillgängligt för kompilatorn att lagra vilken nyckel som skickas. Typargument är som variabler som kompilatorn kan lagra information i som kommer att vara användbar vid vidare kompilering.

Men för fn2 är fn2(people, 'age'):s typargument enligt följande,

<P, 'age'>(p: P, 'age': 'age' | 'name') => p['age'], här är Tx som generiskt argument 'age' vilket kompilatorn vet vid kompileringstid och det kan lagra denna information tillsammans med funktionen vilket hjälper kompilatorn att lösa p['age'] somnumber`.

Svar #2

Om vi byter namn på type T=keyof P till type Keys=keyof P, ser vi ett fel på denna rad:

type Fn1 = (p: P, t: T) => P[T]
                     ~~ Cannot find name 'T'.(2304)

// but this line remains fine
type Fn2 = <P, T extends keyof P>(p: P, t: T) => P[T]

Detta beror på att T i Fn2 är en typparameter medan T i Fn1 är en referens till en typalias (det blir uppenbart när vi senare döper om det till Keys).

Typen av en typparameter är lat-bundet, precis som värdet av en parameter i en funktion bara är känt när funktionen anropas.

Men typen av en typalias är ivrigt bunden om möjligt, precis som när du deklarerar en const-variabel, är dess värde redan känt vid initialiseringen.

Således är ReturnType för Fn1 redan känt som string | number vid deklarationen, medan det för Fn2 är okänt tills det anropas och instansieras någonstans med en konkret typ given till typparametern T.

Kolla in det i lekområdet

type Ret1 = ReturnType<Fn1>   // string | number
type Ret2 = ReturnType<Fn2>   // unknown