Reputation: 550
I'm looking for a way to express the type of [B, C]
from the following code in a generic way. If I hover types
as it currently is I get const types: (typeof B | typeof C)[]
which is a bit verbose and could get very long as new items are added.
abstract class A {
static staticF<T extends A>(this: new () => T): string {
return 'A'
}
}
class B extends A {
static bProp = 1
}
class C extends A {
static cProp = 1
static staticF<T extends A>(this: new () => T): string {
return 'B'
}
}
const types = [B, C]
types
.map(t => t.staticF())
.forEach(x => console.log(x))
I tried using const types: typeof A[]
but I get the following error:
The 'this' context of type 'typeof A' is not assignable to method's 'this' of type 'new () => A'. Cannot assign an abstract constructor type to a non-abstract constructor type.
I also tried const types: typeof extends A[]
but TS thinks I'm drunk.
How can I express the types of multiple classes constructors from classes which share the same parent?
Also, what is the difference between typeof A
, new () => A
and {new (): A}
?
Upvotes: 1
Views: 2361
Reputation: 250046
The simplest part to answer is what is the difference between typeof A
, new () => A
and {new (): A}
. The last two are equivalent, the { new() : A }
syntax is the more verbose cousin of new () => A
. The reason to use the former more verbose version is because is allows you to specify more overloads for the constructor, and would also allow you to specify extra members (ie static methods). typeof A
is the class A
which includes the constructor signature, as well as any statics. If you just care about being able to create instances of the class, the simple constructor signature is good enough. If you need to access the statics as well you need typeof Class
.
As for your other issue, the problem is that typescript treats the constructor of an abstract class as a bit of a second class constructor. It's not a constructor except inside a derived class, which, since the class should not be instantiated, this is not a bad idea. In this case however this means that when calling static
on A
the class A
will not fulfill the constraint that it has a callable constructor (this: new () => T
)
You ca create a type that removes the constructor from the abstract class and then adds it back as non abstract. To remove the constructor you can just use a mapped type suck as Pick
, that will copy the properties but not any function or constructor signatures from a type, and then intersect back with the constructor type:
type DerivedAClass = (new () => A) & Pick<typeof A, keyof typeof A>
let types : DerivedAClass[] = [B, C];
types
.map(t => t.staticF()) // T of staticF will be DerivedAClass
.forEach(x => console.log(x))
The simplest solution, that I found is to create an interface that extends typeof A
. This will in effect erase the abstractness of the constructor and allow you to create the array you want:
type AClass = (typeof A);
interface DerivedAClass extends AClass {}
let types : DerivedAClass[] = [B, C];
types
.map(t => t.staticF()) // T of staticF will be DerivedAClass
.forEach(x => console.log(x))
Upvotes: 4