typescript-book icon indicating copy to clipboard operation
typescript-book copied to clipboard

Mixin

Open basarat opened this issue 9 years ago • 3 comments

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();

basarat avatar Jan 20 '16 23:01 basarat

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 avatar Jan 21 '16 00:01 jods4

@jods4 thanks a lot. I'll wait for consensus :rose:

basarat avatar Jan 21 '16 01:01 basarat

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/

basarat avatar Jan 23 '17 23:01 basarat