I recently ran into a problem and I can't figure out the solution from other questions. So basically what I would like to achieve is:
I have a lot of abstract classes, and I have to create more abstract classes that extends from the others such as this:
abstract class MyAbstractClass extends Mixin(A, B | C | D)
where Mixin is a function from the ts-mixer npm package and A is an abstract class that won't change, it stays the same, however the other class is not known specifically.
I tried it with generics with no luck, I tried creating a factory function, but I can't return an abstract class like this
return abstract class MyAbstractClass extends Mixin(A, param){}
only a non-abstract class.
If somebody could provide an answer or point me in the right direction I would appreciate it
edit: What I'm currently doing is:
abstract class Button{
abstract onToggle():void
}
abstract class A{
abstract foo():void
}
abstract class B{
abstract bar():void
}
abstract class ExtendedButtonA extends Mixin(Button, A){}
abstract class ExtendedButtonB extends Mixin(Button, B){}
So when I make the final class I have to do it like this:
class FinalButtonA extends ExtendedButtonA{}
class FinalButtonB extends ExtendedButtonB{}
What I want to be able to do:
class FinalButton extends ExtendedButton<A>{}
or something like this.
CodePudding user response:
My suggestion is write the ExtendedButton
factory function like this:
function ExtendedButton<T extends abstract new (...args: any) => InstanceType<T>>(
ctor: T
) {
return Mixin(Button, ctor);
}
This seems to work for your example code:
abstract class A {
abstract foo(): void
}
abstract class FinalButton extends ExtendedButton(A) { }
function foo(fb: FinalButton) {
fb.foo();
fb.onToggle();
}
The way I did this was to first see what happens when you call Mixin(Button, A)
directly:
const testA = Mixin(Button, A)
// const testA: Class<any[], Button & A, typeof Button & typeof A, false>
And then compare it to a generic version where instead of A
we pass in a ctor
value of a generic abstract construct signature type:
function ExtendedButton<T extends abstract new (...args: any) => object>(
ctor: T
) {
return Mixin(Button, ctor);
}
const testA2 = ExtendedButton(A);
// const testA2: Class<any[], Button & object, typeof Button & typeof A, false>
The difference here is that the second type argument is Button & object
instead of the desired Button & A
. Why is that? It seems that the compiler is widening the constructor to its constraint abstract new (...args: any) => object
and getting its instance type, prematurely widening all the way to object
when we wanted A
.
Well, if it's going to use that constraint, maybe we can make the constraint itself generic, so that it's still useful. Given a constructorlike type T
, we can use the InstanceType<T>
utility type to express its instance type. So let's change object
to InstanceType<T>
:
function ExtendedButton<T extends abstract new (...args: any) => InstanceType<T>>(
ctor: T
) {
return Mixin(Button, ctor);
}
Luckily that compiles (sometimes recursive constraints don't), and now the call signature looks like
// function ExtendedButton<T extends abstract new (...args: any) => InstanceType<T>>(
// ctor: T
// ): Class<any[], Button & InstanceType<T>, typeof Button & T, false>
where the second type parameter is Button & InstanceType<T>
. Let's try it out:
const testA3 = ExtendedButton(A);
// const testA3: Class<any[], Button & A, typeof Button & typeof A, false>
Perfect, that's the same type as what we get if we call Mixin
directly.