typescript2.0 - Typescript: Skicka generisk klass till funktionsparameter


Jag behöver skicka en klassreferens som en funktionsparameter och anropa en statisk metod på denna klass. I framtiden kan jag behöva skapa en instans av klassen och lägga till en konstruktor som exempel.

Jag har det fungerande utan typningar:

class Messages {
    getMessage(classRef: any): any {
       return classRef.getMessage()
     }
}

class myClassA {
    constructor(public id: number, public message: string) {}
    static getMessage(): string {
        return 'hello from class A';
    }
}

class myClassB {
    constructor(public id: number, public message: string) {}
    static getMessage(): string {
        return 'hello from class B';
    }
}

var messages = new Messages();
var messageA = messages.getMessage(myClassA);
var messageB = messages.getMessage(myClassB);

console.log(messageA) // 'hello from class A'
console.log(messageB) // 'hello from class B'

Jag försöker skriva klassreferensen, möjligtvis med generics men är förvirrad över hur jag ska gå tillväga.

Jag försökte göra något i stil med getMessage<C>(classRef: {new(): C;}): any {}... men inget av det fungerar.

Kan någon snälla förklara hur jag kan skicka en klassreferens på rätt sätt?

Svar #1

Generellt sett måste du referera till klasser i TypeScript med deras konstruktörstyp. Men från och med TypeScript 2.8 finns det en ny InstanceType<T>-typ i standardbiblioteket som kan extrahera instanstypen från konstruktörstypen. Du kan använda den här för att få den typkontroll du vill ha.

För ditt kodsnutt kan du lägga till typer så här:

class Messages {
    getMessage<T extends {getMessage: () => string, new (...args: any[]): InstanceType<T>}>(classRef: T): string {
       // Here, classRef is properly inferred to have a `getMessage` method.
       return classRef.getMessage()
    }
}

class myClassA {
    constructor(public id: number, public message: string) {}
    static getMessage(): string {
        return 'hello from class A';
    }
}

class myClassB {
    constructor(public id: number, public message: string) {}
    static getMessage(): string {
        return 'hello from class B';
    }
}

var messages = new Messages();

// messageA and messageB inferred to have type: string
// You can change that back to any if you want.

// myClassA and myClassB both assignable as the argument to
// getMessage, so no problem there.
var messageA = messages.getMessage(myClassA);
var messageB = messages.getMessage(myClassB);

console.log(messageA) // 'hello from class A'
console.log(messageB) // 'hello from class B'

Linjen

getMessage<T extends {getMessage: () => string, new (...args: any[]): InstanceType<T>}>(classRef: T): string {

är där typ-säkerheten kommer ifrån. Den syntaxen säger att vad än T är, måste det ha en metod getMessage (så om T är en klasskonstruktor, måste getMessage vara en statisk metod), och new (...args: any[]): InstanceType<T> betyder att T måste vara en klasskonstruktor.

Jag har ställt in argumenten för konstruktorn att vara vad som helst här, men om du vet att konstruktorn alltid kommer att ta specifika argument, kan du begränsa det ytterligare. För ditt exempel skulle new (id: number, message: string): InstanceType<T> fungera.

Utan TypeScript 2.8 kan du inte använda InstanceType. Men du kan fortfarande få typ-säkerhet här genom att låta T representera instanstypen, och sedan sätta typen på parametern till en omsluten typ som använder T som dess parameter. Så för ditt exempel:

interface Messageable<T> {
    new(...args: any[]): T;
    getMessage(): string;
}

class Messages {
    getMessage<T>(classRef: Messageable<T>): string {
       return classRef.getMessage()
    }
}

bör fungera.

Relaterade artiklar