Skip to content

Instantly share code, notes, and snippets.

@emilniklas
Last active March 31, 2017 17:00
Show Gist options
  • Save emilniklas/9ee2471b555cb707ff3e868f87b35aeb to your computer and use it in GitHub Desktop.
Save emilniklas/9ee2471b555cb707ff3e868f87b35aeb to your computer and use it in GitHub Desktop.
Prototypal mixins i JavaScript + strong TypeScript types
import { mix } from './mix'
class Mixin1 {
one () {}
}
class Mixin2 {
two () {}
}
class MyClass extends mix(Mixin1, Mixin2) {
three () {}
}
const instance = new MyClass()
instance.one()
instance.two()
instance.three()
import { mix } from './mix'
abstract class Mixin1 {
abstract abstractMethod (): string
implementedMethod () {
return 'value'
}
}
abstract class Mixin2 {
methodFromOtherMixin () {
return true
}
}
class MyClass extends mix(Mixin1, Mixin2) {
abstractMethod () {
return 'this must be implemented, or else TypeScript will complain'
}
}
const instance = new MyClass()
instance.abstractMethod() // 'this must be implemented...'
instance.implementedMethod() // 'value'
instance.methodFromOtherMixin() // true
/**
* Creates a constructor function with
* a prototype chain of all the classes
* passed in.
*
* The mixin functions themselves will
* not be called. Mixins should not have
* constructors. Instead they should
* assume fields or methods on `this`,
* which it is up to the subclass to
* provide.
*
* Conflicting field names in mixins
* can't be resolved. The rightmost mixin
* provided will take precedence.
*
* @param {Array<Function>} ...mixins
*
* @return {Function}
*/
export function mix (...mixins) {
function mix () {}
mix.prototype = mixins
.reduce((o, m) =>
Object.create(o, getOwnPropertyDescriptors(m.prototype)),
{}
)
mix.name = mixins.map(m => m.name).join(' & ')
return mix
}
function getOwnPropertyDescriptors(proto) {
return Object.getOwnPropertyNames(proto)
.reduce((descs, name) => {
descs[name] = Object.getOwnPropertyDescriptor(proto, name)
return descs
}, {})
}
function mix <T1> (
m1: { prototype: T1 },
): { new (): T1 }
function mix <T1, T2> (
m1: { prototype: T1 },
m2: { prototype: T2 },
): { new (): T1 & T2 }
function mix <T1, T2, T3> (
m1: { prototype: T1 },
m2: { prototype: T2 },
m3: { prototype: T3 },
): { new (): T1 & T2 & T3 }
function mix <T1, T2, T3, T4> (
m1: { prototype: T1 },
m2: { prototype: T2 },
m3: { prototype: T3 },
m4: { prototype: T4 },
): { new (): T1 & T2 & T3 & T4 }
function mix <T1, T2, T3, T4, T5> (
m1: { prototype: T1 },
m2: { prototype: T2 },
m3: { prototype: T3 },
m4: { prototype: T4 },
m5: { prototype: T5 },
): { new (): T1 & T2 & T3 & T4 & T5 }
function mix <T1, T2, T3, T4, T5, T6> (
m1: { prototype: T1 },
m2: { prototype: T2 },
m3: { prototype: T3 },
m4: { prototype: T4 },
m5: { prototype: T5 },
m6: { prototype: T6 },
): { new (): T1 & T2 & T3 & T4 & T5 & T6 }
function mix <T1, T2, T3, T4, T5, T6, T7> (
m1: { prototype: T1 },
m2: { prototype: T2 },
m3: { prototype: T3 },
m4: { prototype: T4 },
m5: { prototype: T5 },
m6: { prototype: T6 },
m7: { prototype: T7 },
): { new (): T1 & T2 & T3 & T4 & T5 & T6 & T7 }
function mix <T1, T2, T3, T4, T5, T6, T7, T8> (
m1: { prototype: T1 },
m2: { prototype: T2 },
m3: { prototype: T3 },
m4: { prototype: T4 },
m5: { prototype: T5 },
m6: { prototype: T6 },
m7: { prototype: T7 },
m8: { prototype: T8 },
): { new (): T1 & T2 & T3 & T4 & T5 & T6 & T7 & T8 }
export { mix }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment