I was learning about Mixins in JavaScript and decided to implement them in TypeScript. Everything works fine when I declare classes normally, but when I assign them to variables, I encounter unexpected behavior. The Mixin methods become inaccessible on objects instantiated from a class combined with a Mixin.
I want to investigate the root cause of this issue and understand why Mixin methods become inaccessible in this scenario.
// Mixin declaration
interface SpeechMixin {
greetings(): string;
}
const speechMixin: SpeechMixin = {
greetings() {
return `Hello, my name is ${this.name}`;
},
};
// Class expression
const User = class {
constructor(public name: string) {}
};
Object.assign(User.prototype, speechMixin);
interface User extends SpeechMixin {}
const george = new User('George');
console.log(george.greetings()); // ERROR: Property 'greetings' does not exist on type 'User'.
While you cannot merge an interface with a class variable with the same name (thus no greetings
) you could create a function that would mix objects into a prototype and assert that:
Playground (oops, swap src
and dst
please)
function mixin<M extends object>(src: new (...args: any) => any, dst: M): asserts src is new (...args: any) => unknown & M {
Object.assign(src.prototype, dst);
}
mixin(User, speechMixin);
IMPORTANT
Actually your pattern could be NOT a mixin since you modify class prototypes, that would be considered as usual inheritance. A mixin is something that you construct from independent objects on the fly:
const speechMixin = class {
declare name: string;
greetings() {
return `Hello, my name is ${this.name}`;
}
};
const User = class {
constructor(public name: string) {}
};
function mixin<C extends object, M extends object>(dst: C, src: M): asserts dst is C & M {
Object.assign(src, dst);
}
const george = new User('George');
mixin(george, new speechMixin);
console.log(george.greetings());