The issue lies in the fact that a mixin will result in an intersection of what it adds with the original constructor type T
. This functions correctly in a non-generic setting, creating a merged class. However, when used within another mixin, T
is unknown, causing the intersection mxinStuff & T
to be unresolvable:
function mixin1<T extends new (... a: any[]) => any> (ctor: T) {
return class WithMixin1 extends ctor {
mixin1() {}
}
}
function mixinMixed<T extends new (... a: any[]) => any> (ctor: T) {
return class WithMixin2 extends mixin1(ctor) { // Type '{ new (...a: any[]): mixin1<T>.WithMixin1; prototype: mixin1<any>.WithMixin1; } & T' is not a constructor function type.
mixin2() {}
}
}
To address this, we can manipulate the type returned by mixin1
, making it usable as a base type for a new class and asserting its similarity to extending the original way:
function mixin1<T extends new (... a: any[]) => any> (ctor: T) {
return class WithMixin1 extends ctor {
mixin1() {}
static staticMixin1() {}
}
}
const mixin1BaseType = () => mixin1(class{});
type Mixin1Type = ReturnType<typeof mixin1BaseType>
function mixinMixed<T extends new (... a: any[]) => {a : string }> (ctor: T) {
class WithMixin2 extends (mixin1(ctor) as unknown as {
new (... a: any[]): {a : string }
} & Mixin1Type ) {
mixin2() {
this.a
this.mixin1()
WithMixin2.staticMixin1();
}
static staticMixin2() {}
}
return WithMixin2 as unknown as {
new (...a: ConstructorParameters<T>): WithMixin2 & InstanceType<T>
} & T & Mixin1Type & typeof WithMixin2
}
type AnyContructor = new (... a: any[]) => any
let m = mixinMixed(class {
a: string = "a";
b: string = "b";
m() {}
static staticM() {}
})
let s = new m();
// Access instance members
s.a
s.b
s.m();
s.mixin1();
s.mixin2();
// Access Static methods
m.staticMixin1()
m.staticMixin2()
m.staticM();
console.log(s)