Home > Mobile >  Creating a function that takes the constructor of a generic class
Creating a function that takes the constructor of a generic class

Time:12-22

The problem

I have the following setup (Playground link):

class Foo<T> {
    constructor(x: T) {
        console.log(x);
    }
}

class Bar extends Foo<string> {

}

Now I want to create a function that takes any extended constructor of Foo.

Instances work fine:

function takesFoo(x: Foo<any>) {
    // ...
}
takesFoo(new Bar("hi"));

But if I want to take a constructor it fails:

function takesFooConstructor(x : typeof Foo) {
    // ...
}
takesFooConstructor(Bar);
//                  ^^^------ Argument of type 'typeof Bar' is not 
//                            assignable to parameter of type 'typeof Foo'.

Why does this not work and how can I fix this?

Some things I already tried

// Maybe I should make the function a generic?
function takesFooCtorGeneric<T extends typeof Foo>(x: T) {
    // ...
}
takesFooCtorGeneric(Bar);
//                  ^^^----- nope
// This works, but I don't want to cast every time I call the function.
takesFooConstructor(Bar as typeof Foo);
function parametersOnly<T extends Foo<any>>(x: (...args: any) => T) {
    // ...
}
parametersOnly(Bar)
//             ^^^------ Type 'typeof Bar' provides no match for the
//                       signature '(...args: any): Foo<any>'.
// This works, but now I can't access static properties.
type Constructor = Function & { prototype: Foo<any> }
function usePrototype(ctor: Constructor) {
    console.log(ctor.someStaticProp);
    //               ^^^^^^^^^^^^^^------ Property 'someStaticProp' does
    //                                    not exist on type 'Constructor'
}
usePrototype(Bar);

CodePudding user response:

Constructors of classes in TypeScript has following signature: new and call signature. In your case, constructor of Bar has type new (x: string) => Bar.

If you want takesFooConstructor take Foo constructor:

function takesFooAndExtendersOfFooConstructorWitoutDefinedT(x : new <T>(x: T) => Foo<T>)

But in that case takesFooConstructor cannot take Bar constructor, because you have already defined T type parameter of Foo.

If you want takesFooConstructor take Foo constructor and constructors of any class that extends Foo:

function takesAnyConstructorsOfFooAndExtendersOfFoo<T>(x : new (x: T) => Foo<T>)

Playground with both approaches

CodePudding user response:

The problem is Foo is generic. This means takesFooConstructor would expect to receive a generic class it would work Ex. This is because takesFooConstructor could instantiate the class passed in as a parameter with any T, but Bar only accepts string.

You can use a constructor signature instead, although you will have to rewrite the constructor arguments:


function takesFooConstructor<T>(x : new (x: T) => Foo<T>) {
    
}
takesFooConstructor(Bar);
takesFooConstructor(Foo);

Playground Link

  • Related