typescript-book
typescript-book copied to clipboard
Mixin
Here is a good solution : https://github.com/Microsoft/TypeScript/issues/6502#issuecomment-173312747 that uses Object.assign
https://github.com/Microsoft/TypeScript/issues/2919#issuecomment-173384825
function Mixin<T>(...mixins) : new() => T {
class X {
constructor() {
mixins[0].call(this);
}
}
Object.assign(X.prototype, ...mixins.map(m => m.prototype));
return <any>X;
}
abstract class Dog {
bark() { console.log('bark!'); }
}
abstract class Duck {
quack() { console.log('quack!'); }
}
class Base {
constructor() {
console.log('base ctor');
}
world = "world";
hello() {
console.log(`hello ${this.world}`);
}
}
interface DoggyDuck extends Base, Dog, Duck { }
class Mutant extends Mixin<DoggyDuck>(Base, Dog, Duck) {
}
let xavier = new Mutant();
xavier.bark();
xavier.quack();
xavier.hello();
Please keep in mind this was a quick proof of concept made for the discussion in Microsoft/Typescript#6502. There are several aspects that are debatable, in particular the signature and implementation of the Mixin helper function. I tried to go for the most straightforward demo possible.
A first key improvement would be making the base class optional.
Please also pay attention to this comment where I hint at some languages improvements that may make this even safer and better.
The gist of the comment is that I couldn't find a solution other than introducing a dummy, empty DoggyDuck interface for each mixins combination.
If the TS compiler could see T & U as a "class or interface" when T or U is a "class or interface", then it would be possible to do this, which does not currently compile:
function Mixin<A,B,C>(a: () => A, b: () => B, c: () => C): () => A & B & C { ... }
// Then without an additional interface:
class X extends Mixin(Base, Dog, Duck) { ... }
Note that I did not think this through, so maybe it's non-sense ;)
Big drawback is that you would need one overload for each arity... I don't think that the variadic proposal for 1.9 will help here because there's no way to transform <...T> into T[0] & T [1] & T[2].
@jods4 thanks a lot. I'll wait for consensus :rose:
This is the placeholder we need to update : https://github.com/basarat/typescript-book/blob/0dbdc38c0df33096797beffc7ce4b9d86ce239e2/docs/tips/mixins.md
There is new work on TypeScript master that makes the mixin pattern easier : https://github.com/Microsoft/TypeScript/pull/13604 :rose:
The new pattern is also covered in a blog post : https://blogs.msdn.microsoft.com/typescript/2017/02/02/announcing-typescript-2-2-rc/