-
-
Save emilniklas/9ee2471b555cb707ff3e868f87b35aeb to your computer and use it in GitHub Desktop.
Prototypal mixins i JavaScript + strong TypeScript types
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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 | |
}, {}) | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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