I'm trying to type a multi-mixin helper function that accepts a map of constructors and returns a map of those constructors extended with some new interface.
Consider the following contrived base classes:
class Alpha { alpha: string = 'alpha'; };
class Beta { beta: string = 'beta'; };
I have a mixin helper function that accepts a map of these classes and returns optionally extended versions of them. Without typings, it looks like this:
// The multi-mixin helper function:
const Fooable = ({ Alpha, Beta }) => {
const AlphaWithFoo = class extends Alpha {
foo = 'foo';
};
return { Alpha: AlphaWithFoo, Beta };
}
// When invoked, it might be used like this:
const { Alpha: FooAlpha, Beta: FooBeta } = Fooable({ Alpha, Beta });
const fooAlpha = new FooAlpha();
// fooAlpha has both "alpha" and "foo" propoerties:
console.log(fooAlpha.alpha, fooAlpha.foo);
However, when I fully type this helper function out, my typings lead to a very strange not-assignable compiler error. Here is a link to the TypeScript compiler playground where you can see the typings I'm using, and the associated error:
The error I'm getting reads:
Type '<A extends Alpha, B extends Beta>({ Alpha, Beta }: Domain<A, B>) => { Alpha: typeof AlphaWithFoo;...' is not assignable to type 'DomainExtender<Foo, {}>'.
Type '{ Alpha: typeof AlphaWithFoo; Beta: Constructor<B>; }' is not assignable to type 'ExtendedDomain<A, B, Foo, {}>'.
Types of property 'Alpha' are incompatible.
Type 'typeof AlphaWithFoo' is not assignable to type 'Constructor<A & Foo>'.
Type 'AlphaWithFoo' is not assignable to type 'A & Foo'.
This error seems kind of strange to me. It is as though the compiler is losing track of the type information for the Alpha
class or something. But, it is probably more likely that I am making a mistake somewhere.
Can anyone help me figure out where I went wrong?
There is a quirk in the way you can derive from a generic parameter, the generic parameter must extend new(...args: any[]) => any
(parameters MUST be ...args: any[]
). We can see this in action in this PR. To get around this we can use a helper function inside the Fooable
function:
const Fooable: DomainExtender<Foo, {}> = ({ Alpha, Beta }) => {
function mixin<U extends Constructor>(Base: U) {
return class extends Base {
constructor(...args: any[]) {
super(...args);
}
foo: string = 'foo';
}
}
const AlphaWithFoo = mixin(Alpha);
return { Alpha: AlphaWithFoo, Beta };
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With