简体   繁体   中英

Typescript: Pass generic class to function parameter

I need to pass a class reference as a function parameter and invoke a static method on this class. In the future, I might need to create an instance of the class and added a constructor as example.

I have it working without typings:

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'

I am trying to type the class reference, possibly with generics but confused about how to go about this.

I tried doing something along the lines of getMessage<C>(classRef: {new(): C;}): any {}... but none of that is working.

Can someone please explain how I can pass a class reference properly?

Generally speaking, you have to refer to classes in typescript using their constructor type. However, as of typescript 2.8, there is a new InstanceType<T> type in the standard lib that can extract the instance type from the constructor type. You can use that here to get the type safety you want.

For your snippet, you might add types like so:

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'

The line

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

is where the type safety comes from. That syntax says that whatever T is, it has to have a method getMessage (so if T is a class constructor, getMessage has to be a static method), and the new (...args: any[]): InstanceType<T> means that T has to be a class constructor.

I've set the arguments for the constructor to be anything here, but if you know that the constructor will always take specific arguments, you could further narrow that. For your example, new (id: number, message: string): InstanceType<T> would work.


Without typescript 2.8, you can't use InstanceType . But you can still get type safety here by letting T represent the instance type, and then setting the type of the parameter to be a wrapped type that uses T as it's parameter. So for your example:

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

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

should work.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM