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<{}>;

Relaterade artiklar