TypeScript: Hur man härleder typen av en generisk baserat på en annan generisk?
Jag har den här koden:
class A<T> {
a(t: T) {
console.log(t);
}
}
class B<T, O extends {b: T}> {
constructor(public a: A<O>) {
}
b(t: T) {
console.log(t)
}
}
const a = new A<{b: number}>(); // type: A<{ b: number; }>
const b = new B(a); // type: B<unknown, { b: number; }>
Varför markerar TypeScript argumentet till metoden b i klassen B som okänt?
Svar #1
Vi har typerna T och O där O extends {b: T}. Förhållandet mellan T och O['b'] är att O['b'] extends T. Detta betyder att T kan vara exakt O['b'] men det kan också vara vilken typ som helst som är bredare än O['b'].
Denna riktning av inferens är omöjlig för typescript att härleda eftersom det finns oändligt många typer för T sådan att number extends T. Du skulle kunna ha en väldigt bred typ som any eller {} som föreslagits av @md2perpe. Du skulle kunna ha en union som inkluderar number som string | number, etc.
För att kunna härleda den andra parametern behöver vi att extends går åt andra hållet. Vi behöver veta att T måste vara snävare än O['b']. Vi kan skriva det så här:
class B<T extends O['b'], O extends {b: any}> {
Här säger vi att O är ett objekt med en b-egenskap. Vi säger att T är en typ som antingen är b-värdet av O eller en delmängd av det. Nu kommer TypeScript att härleda T som O['b'].
const a = new A<{b: number}>(); // type: A< b: number; }>
const b = new B(a); // type: B<number, { b: number; }>
b.b(5) // takes type: number
Observera att du fortfarande kan manuellt ställa in T till en smalare typ:
const specificB = new B<5, {b: number}>(a); // type: B<5, { b: number; }>
specificB.b(10); // error: Argument of type '10' is not assignable to parameter of type '5'.
Typescript Playground-länk
Svar #2
Tror du att det borde härledas att T=number? Om villkoret vore O equals { b: T } (vilket du inte kan skriva), då skulle det vara sant. Men eftersom number extends {} kan vi lika gärna ha T={}.
I den här koden kommer g att ha typen true:
type G<T> = { b: number } extends { b: T } ? true : false;
let g: G<{}>;
