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
