TypeScript tutorial: Lesson 30 – Mixins


A mixin is an abstract subclass; i.e. a subclass definition that may be applied to different superclasses to create a related family of modified classes.

(Gilad Bracha and William Cook, Mixin-based Inheritance)

How mixins can be implemented in TypeScript:
We introduce 2 methods to implement mixins in TypeScript

Method 1: Use subclass factory

Example 1:

 
type Contructor = new(...args:any[])=>{}
function mixin1<T extends Contructor,U extends Contructor>(Base: T, Mix: U) {
    var SubClass = class extends Base {          
    }    
    Object.assign(SubClass,Mix)    
    Object.assign(SubClass.prototype,Mix.prototype)    
    
    return SubClass
}

class A{
    static A1=1
    toString(){
        return 'A'
    }
}

class B{
    static B1=2
    toString(){
        return 'B'
    }
    doJob(){
        console.log('B do job')
    }
}

var C = mixin1(A,B)
var c = new C()

console.log(C.A1,C.B1)
c.doJob()

Result:

1 2
B do job

Example 2: use Mixin with class decorator

function mixin2<U extends Contructor>(Mix: U) {
    return function<T extends Contructor>(Base: T){
        var SubClass = class extends Base {          
        }    
        Object.assign(SubClass,Mix)    
        Object.assign(SubClass.prototype,Mix.prototype)            
        return SubClass
    }
}

@mixin2(B)
class A2{
    static A1=1
    toString(){
        return 'A'
    }
}

var a2 = new A2()
console.log(a2.toString())
a2.doJob()

Result:

B
B do job

Note: Mixins doesn’t support public properties, see Example 2a about this problem, and see Example 2b for fix this problem
Example 2a:

class B3{
    static B3=1
    propB3=1
    toString(){
        return 'B3'
    }
}

@mixin2(B3)
class A6{
    static A1=1
    prop=1
    toString(){
        return 'A'
    }
}

var a6 = new A6()
console.log(a6.propB3) //error TS2551: Property 'propB3' does not exist on type 'A6'.

Example 2b: Use intersection types and assert type

class A7{
    static A7=1
    propA7=1
    toString(){
        return 'A7'
    }
}

interface B4{
    propB4:number
}

type MixinA7B4 = typeof A7 & B4
 
const instanceMixinA7B4 = (new A7() as unknown) as MixinA7B4
instanceMixinA7B4.propB4

Example 3: Multiple Mixins and Class decorator

class B2{
    static B2=3
    toString(){
        return 'B2'
    }
    doJob(){
        console.log('B2 do job')
    }
}

@mixin2(B2) @mixin2(B)
class A3{
    static A1=1
    toString(){
        return 'A'
    }
}

var a3 = new A3()
console.log(a3.toString())
a3.doJob()

Result:

B2
B2 do job

Example 4: Multiple mixins

class A4 extends mixin2(B2)(B){
    static A1=1
    toString(){
        return 'A'
    }
}

var a4 = new A4()
console.log(a4.toString())
a4.doJob()

Result:

A
B2 do job

Method 2: Use mixin builder
Example 5:

class MixinBuilder {
    superclass:any
    constructor(superclass:any) {
      this.superclass = superclass
    }

    mixin<T extends Contructor,U extends Contructor>(Base: T, Mix: U) {
        var SubClass = class extends Base {          
        }    
        Object.assign(SubClass,Mix)    
        Object.assign(SubClass.prototype,Mix.prototype)            
        return SubClass
    }
  
    mixins(...classes:any[]) { 
      return classes.reduce((a, b) => this.mixin(a, b), this.superclass)
    }
}

let mix = (superclass:any) => new MixinBuilder(superclass)

class A5 extends mix(B).mixins(B2){
    static A1=1
    toString(){
        return 'A'
    }
}

var a5 = new A5()
console.log(a5.toString())
a5.doJob()

Result:

A
B2 do job

Constrained Mixins

In these above examples , we define a type:

type Contructor = new(...args:any[])=>{}

It’s equal to

interface IContructor{
    new(...args:any[]):{}
}

or

interface IContructor2{
    new:(...args:any[])=>{}
}

or

type Contructor2 = {
    new(...args:any[]):{}
}

or

type Contructor3 = {
    new:(...args:any[])=>{}
}

We can use a generic version which can apply a constraint on the class which this mixin is applied to

type GConstructor<T> = new (...args: any[]) => T

Now, we can create some type to work with contrained base classes use with mixins:

type Positionable = GConstructor<{ setPos: (x: number, y: number) => void }>
type Spritable = GConstructor<typeof Sprite>
type Loggable = GConstructor<{ print: () => void }>

Leave a Reply