There is an article on using and creating mix-ins on MDN (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends#mix-ins). I've tried to implement this in TypeScript:
type Constructor = new (...args: any) => any;
function nameMixin(Base: Constructor) {
return class extends Base {
#name?: string;
name() {
return this.#name;
}
setName(name: string) {
this.#name = name;
}
};
}
class None {}
class Foo extends nameMixin(None) {
constructor(private value: number) {
super();
}
}
const foo = new Foo(10)
foo.setName("john")
foo.name()
This seems to work pretty well but the one thing I don't particularly like about this trick is that I have to extend the arbitrary None
class.
I've seen other people implement mix-ins with Object.assign(Foo.prototype, mixin)
, where mixin
is an object, but I don't like that because it decouples the mix-in from the declaration.
Anyone know any other, cleaner ways of implementing mix-ins?
"Anyone know any other, cleaner ways of implementing mix-ins?"
The, in my opinion, still most versatile pattern of the many different ways of accomplishing mixins in JavaScript is a function based mixin which is aware of a this
context, thus it always gets applied via call
to the object in need of a mixed-in behavior.
In addition of always working upon a delegated/bound this
, one, at apply time, can either pass variables like string and number values or even object-references which could be used as privately shared (even mutable) state amongst other such alike implemented mixins and the classes/factories which do create objects with mixed-in behavior.
// `this` context aware function based mixin.
function withSetGetName(name = '') {
// delegated/bound `this` context.
Reflect.defineProperty(this, 'name', {
// returning the locally scoped variable.
get: () => name,
// - controlling whether and how to change the
// locally scoped (private) variable's value.
set: (value) => (name = value),
});
return this;
}
class Foo {
#number;
constructor(name, number) {
this.#number = number;
// applying the function based mixin.
withSetGetName.call(this, name);
}
}
// instance of class which does mixin-in.
const foo = new Foo('Jim', 10);
// a plain object with mixed-in behavior.
const bar = withSetGetName.call({});
console.log(
'foo.name =>', foo.name
);
console.log(
"foo.name = 'John' =>", (foo.name = 'John')
);
console.log(
'foo.name =>', foo.name
);
console.log(
'bar.name =>', bar.name
);
console.log(
"bar.name = 'Jane' =>", (bar.name = 'Jane')
);
console.log(
'bar.name =>', bar.name
);
.as-console-wrapper { min-height: 100%!important; top: 0; }