typescript - fel vid försök att returnera InstanceType av utökad klass


Så här är min kod

class Base {
    constructor (public name : string) {}
}
class Extended extends Base {
    get manged_name () {
        return this.name;
    }
}

function GENERATE<T extends typeof Base> (Model : T) : InstanceType<T> {
    const instance = new Model("something");
    return instance;
}
GENERATE(Extended);

notera: GENERATE har förenklats kraftigt för exempeländamål

Felet jag får är Typ 'Base' kan inte tilldelas typen 'InstanceType<T>'.

Förväntad

Det jag försöker göra är att tillåta att alla barnklasser till Base kan användas som en parameter, och att dess instans returneras. Varför ger det fel?

tslekplats

Svar #1

Inuti din generate()-funktion är den härledda typen av new Model("something") bara Base. Det beror på att åtkomst till konstruktorsignaturen på en generisk konstruktor reducerar den konstruktorn till dess begränsning. Se ms/TS#47502 för en källa. Så istället för att vara specifik för T, har kompilatorn breddat instanstypen hela vägen till Base.

Dessutom, eftersom verktygstypen InstanceType<T> är implementerad som en villkorlig typ på detta sätt:

type InstanceType<T extends abstract new (...args: any) => any> = 
  T extends abstract new (...args: any) => infer R ? R : any;

och eftersom T-typen inuti kroppen av din generate()-funktion är en generisk typparametern, gör det InstanceType<T> till en villkorlig typ som beror på en generisk typparameter. Och, tyvärr, kan TypeScript-kompilatorn egentligen inte verifiera att något är tilldelningsbart till sådana typer. De är i princip ogenomskinliga för kompilatorn. Se microsoft/TypeScript#48234, microsoft/TypeScript#48746 och utan tvekan många andra som källa för det.


Det betyder att du försöker tilldela den för breda typen Base till den opaka typen InstanceType<T>, och det misslyckas. Om vi vill att kompilatorn ska kunna följa din logik, skulle jag rekommendera att refaktorisera så att den generiska typparametern motsvarar instanstypen istället för konstruktörstypen, på detta sätt:

function generate<T extends Base>(Model: new (name: string) => T): T {
    const instance = new Model("something");
    return instance; // okay
}
generate(Extended); // okay

Observera hur parametern Model ges en konstruktionssignatur som returnerar typen T när den anropas. Det betyder att new Model("something") producerar ett värde av typen T, vilket är precis vad vi vill ha.

Playground-länk till kod