Reputation: 3867
I have the following code:
class Base<TAttributes extends {} = {}> {
constructor(attributes: TAttributes) {}
}
class Extended extends Base<{test: string}> {
}
function test(test: typeof Base) {};
test(Extended);
What I am trying to achieve, is to create a function, which expects a constructor of Base
class type, and I am trying to pass it constructors of more specific (extended) types. Unfortunatly, because of the generics, typescript complains, that:
Argument of type 'typeof Extended' is not assignable to parameter of type 'typeof Base'. Types of construct signatures are incompatible. Type 'new (attributes: { test: string; }) => Extended' is not assignable to type 'new <TAttributes extends {} = {}>(attributes: TAttributes) => Base'. Types of parameters 'attributes' and 'attributes' are incompatible. Type 'TAttributes' is not assignable to type '{ test: string; }'. Property 'test' is missing in type '{}' but required in type '{ test: string; }'
I can't figure out, what am I doing wrongly here. If I remove the generic parameters from the base class, typescript is not complaining anymore. Could someone point me in the direction, on how to achieve my desired outcome?
Here's a playground link also.
Upvotes: 3
Views: 2432
Reputation: 31815
If you need to use only the static
methods from a base class
and not its constructor
, you can use a NoConstructor
helper type in order to keep only the properties from the base class, and remove the constructor function part of it:
class Base<TAttributes extends {} = {}> {
static log() {
console.log('log');
}
constructor(attributes: TAttributes) { }
}
class Extended extends Base<{ test: string }> {
}
type NoConstructor<T> = Pick<T, keyof T>;
function test(test: NoConstructor<typeof Base>) {
test.log(); // OK
};
test(Extended); // OK
Upvotes: 1
Reputation: 187034
Try to write the implementation of test()
. How could you possibly know what arguments to pass to the constructor?
In your example Base
could be instantiated by:
new Base()
But Extended
requires a { test: string }
argument.
new Extended({ test: 'qwe' })
So you cannot treat the Extended
constructor like the Base
constructor. It has a different type.
Instances of a child class are always assignable to types of the parent class, but the same is not true for constructors which can change drastically.
You could do something like:
function test<
Args extends unknown[],
Instance extends unknown
>(
ctor: new (...args: Args) => Instance,
...args: Args
): Instance {
return new ctor(...args)
}
In this example, the test function takes a ctor
(any constructor) and args
(the arguments that constructor requires). And then returns whatever Instance
types that constructor makes.
// works
test(Base, {});
test(Extended, { test: 'qwe' });
But this will work with any constructor, and not just whatever some Base
is.
Upvotes: 0