I tried to define type-safe mixin()
decorator function like follows,
type Constructor<T> = new(...args: any[]) => T;
function mixin<T>(MixIn: Constructor<T>) {
return function decorator<U>(Base: Constructor<U>) : Constructor<T & U> {
Object.getOwnPropertyNames(MixIn.prototype).forEach(name => {
Base.prototype[name] = MixIn.prototype[name];
});
return Base as Constructor<T & U>;
}
}
And used it as follows,
class MixInClass {
mixinMethod() {console.log('mixin method is called')}
}
/**
* apply mixin(MixInClass) implicitly (use decorator syntax)
*/
@mixin(MixInClass)
class Base1 {
baseMethod1() { }
}
const m1 = new Base1();
m1.baseMethod1();
m1.mixinMethod(); // error TS2339: Property 'mixinMethod' does not exist on type 'Base1'.
Then, compiler said m1
didn't have the member 'mixinMethod'
.
And generated code is as follows,
//...
var Base1 = /** @class */ (function () {
function Base1() {
}
Base1.prototype.baseMethod1 = function () { };
Base1 = __decorate([
mixin(MixInClass)
], Base1);
return Base1;
}());
//...
It looks that mixin
decorator was applied correctly.
So, in my understanding, the type of m1
is inferred as Base1 & MixIn
. But compiler says it's just Base1
.
I used tsc 2.6.2
and compiled these codes with --experimentalDecorators
flag.
Why does compiler fail to recognize the type as I expected?
Based on @jcalz's answer, I modified my code as follows,
type Constructor<T> = new(...args: any[]) => T
function mixin<T1, T2>(MixIns: [Constructor<T1>, Constructor<T2>]): Constructor<T1&T2>;
function mixin(MixIns) {
class Class{ };
for (const MixIn of MixIns) {
Object.getOwnPropertyNames(MixIn.prototype).forEach(name => {
Class.prototype[name] = MixIn.prototype[name];
});
}
return Class;
}
class MixInClass1 {
mixinMethod1() {}
}
class MixInClass2 {
mixinMethod2() {}
}
class Base extends mixin([MixInClass1, MixInClass2]) {
baseMethod() { }
}
const x = new Base();
x.baseMethod(); // OK
x.mixinMethod1(); // OK
x.mixinMethod2(); // OK
x.mixinMethod3(); // Property 'mixinMethod3' does not exist on type 'Base' (Expected behavior, Type check works correctly)
This works pretty well. I want to improve this mixin
function for variable length mixin classes.
One solution is adding overload function declaration like follows,
function mixin<T1>(MixIns: [Constructor<T1>]): Constructor<T1>;
function mixin<T1, T2>(MixIns: [Constructor<T1>, Constructor<T2>]): Constructor<T1&T2>;
function mixin<T1, T2, T3>(MixIns: [Constructor<T1>, Constructor<T2>, Constructor<T3>]): Constructor<T1&T2&T3>;
But this is too ugly. Are there any good ideas? Is it impossible until variadic-kind is supported?
Mixins are a faux-multiple inheritance pattern for classes in JavaScript which TypeScript has support for. The pattern allows you to create a class which is a merge of many classes. To get started, we need a type which we'll use to extend other classes from.
Using Decorator Syntax In TypeScript, you can create decorators using the special syntax @expression , where expression is a function that will be called automatically during runtime with details about the target of the decorator. The target of a decorator depends on where you add them.
Therefore, TypeScript provides mixins that help to inherit or extend from more than one class, and these mixins create partial classes and combine multiple classes to form a single class that inherits all the functionalities and properties from these partial classes.
Since decorators are an experimental feature, they are disabled by default. You must enable them by either enabling it in the tsconfig. json or passing it to the TypeScript compiler ( tsc ).
Decorators don't mutate the type signature of the decorated class the way you're expecting. There's a rather lengthy issue in Github which discusses this, and it's not clear there's agreement on how (or if) such mutation should be implemented. The main problem right now is that the compiler understands Base1
as the undecorated class, and doesn't have a name for the decorated version.
From reading that Github issue, it looks like the suggested workaround (for now at least) is something like:
class Base1 extends mixin(MixInClass)(
class {
baseMethod1() { }
}) {
}
So you're not using the decorator @
notation, and instead directly applying the decorator function to an anonymous class (which has the same implementation of your desired Base1
), and then subclassing that to get Base1
. Now the compiler understands that Base1
has both a baseMethod1()
and a mixinMethod()
.
Hope you find that helpful. Good luck!
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