1.3.3 • Published 8 years ago

sirrobert-mixin v1.3.3

Weekly downloads
3
License
MIT
Repository
bitbucket
Last release
8 years ago

Rationale

ECMAScript 6 has classes and inheritance (via extends). It should also have mixins (that I like). Now it does.

Installation

Local installation

npm install --save sirrobert-mixin

Global installation

npm install --global sirrobert-mixin

Usage

Writing a Good Mixin

The key to writing a good mixin is to make it a composable function. The idea is that the mixin definition is actually a factory rather than a function itself.

Mixins are Factories (not functions)

Here's the pattern:

let MyMixin = (superclass) => class extends superclass {
  constructor (params) {
    super(params);
    // ... and other stuff
  }
}

When you use the Mixin.with() method (or class method), you are passing in the superclass and it does a little magic inside to create a new inheritance chain. In particular, it looks like this:

// Class definition
class B extends Mixin(A).with(X,Y) { ... }

// Inheritance chain
B > X > Y > A

If you want a chain more like this:

// Mixins are after A
B > A > X > Y

You would use this:

class A extends Mixin.with(X, Y) {}
class B extends A {}

Building some Classes

Let's build some classes and see the inheritance chain in action. First, we load the module and define a base class and some mixins.

// I like to use "Mix" because it's a short verb.
const Mix = require("sirrobert-mixin");

class A {
  constructor () {
    this.foo = "Foo from A";
    this.a = "only in A";
  }
}

let Mixin1 = (superclass) => class extends superclass {
  constructor (params) {
    super(params);
    this.foo = "Foo from Mixin1";
    this.mixin1 = "only in mixin1";
  }
}

let Mixin2 = (superclass) => class extends superclass {
  constructor (params) {
    super(params);
    this.foo = "Foo from Mixin2";
    this.mixin2 = "only in mixin2";
  }
}

// This one doesn't have a .foo property so we can see the transparency.
let MixinNoFoo = (superclass) => class extends superclass {
  constructor (params) {
    super(params);
    this.mixinNoFoo = "only in mixinNoFoo";
  }
}

Now let's see how we can mix and match these into new classes.

Create a class that uses one mixin.

class MyClass extends Mix.with(Mixin1) {
  constructor (params) {
    super(params);
    this.myClass = "only in myClass";
  }
}

console.log(new MyClass().myClass);  // "only in myClass"
console.log(new MyClass().foo);      // "Foo from Mixin1"

Create a class with multiple mixins

With multiple mixins, the earlier mixins in the list override the latter.

class MyClass extends Mix.with(Mixin1, Mixin2) {
  constructor (params) {
    super(params);
    this.myClass = "only in myClass";
  }
}

console.log(new MyClass().myClass);  // "only in myClass"
console.log(new MyClass().foo);      // "Foo from Mixin1"
console.log(new MyClass().mixin1);   // "only in Mixin1"
console.log(new MyClass().mixin2);   // "only in Mixin2"

Create a class with superclass and mixins

When extending another class, you can insert mixins into the chain.

class MyClass extends Mix(A).with(Mixin1, Mixin2) {
  constructor (params) {
    super(params);
    this.myClass = "only in myClass";
  }
}

console.log(new MyClass().myClass);  // "only in myClass"
console.log(new MyClass().foo);      // "Foo from Mixin1"
console.log(new MyClass().mixin1);   // "only in Mixin1"
console.log(new MyClass().mixin2);   // "only in Mixin2"
console.log(new MyClass().a);        // "only in A"

Sugar

The .with() method is available as an instance method and a class method. This allows you to add mixins when you have a base class, but also when you just want the mixins. These are equivalent, but I think the second looks neater, having less syntactic noise.

class B extends Mix().with(X,Y) {}
class B extends Mix.with(X,Y) {}