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);