Mixins
introduce
In addition to the traditional object-oriented inheritance, there is also a popular way to create classes through reusable components, which is to combine the code of another simple class. You may be familiar with mixins and its features in languages such as Scala, but it is also very popular in JavaScript.
Blend in Example
The following code demonstrates how to use blending in TypeScript. We'll explain how this code works later.
// Disposable Mixin class Disposable { isDisposed: boolean; dispose() { this.isDisposed = true; } } // Activatable Mixin class Activatable { isActive: boolean; activate() { this.isActive = true; } deactivate() { this.isActive = false; } } class SmartObject implements Disposable, Activatable { constructor() { setInterval(() => console.log(this.isActive + " : " + this.isDisposed), 500); } interact() { this.activate(); } // Disposable isDisposed: boolean = false; dispose: () => void; // Activatable isActive: boolean = false; activate: () => void; deactivate: () => void; } applyMixins(SmartObject, [Disposable, Activatable]); let smartObj = new SmartObject(); setTimeout(() => smartObj.interact(), 1000); // In your runtime library somewhere function applyMixins(derivedCtor: any, baseCtors: any[]) { baseCtors.forEach(baseCtor => { Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => { derivedCtor.prototype[name] = baseCtor.prototype[name]; }); }); }
Understand this example
The code first defines two classes, which will be called mixins. You can see that each class defines only one specific behavior or function. We'll use them later to create a new class with both functions.
// Disposable Mixin class Disposable { isDisposed: boolean; dispose() { this.isDisposed = true; } } // Activatable Mixin class Activatable { isActive: boolean; activate() { this.isActive = true; } deactivate() { this.isActive = false; } }
Now create a class that combines these two mixins. Here's how to operate:
class SmartObject implements Disposable, Activatable {
The first thing you should notice is that instead of extensions, you use implements. Treat the class as an interface and only use the types of Disposable and Activatable instead of their implementation. This means that we need to implement the interface in the class. But this is what we want to avoid when using mixin.
We can do this to create placeholder attributes for the attribute methods that will mixin in. This tells the compiler that these members are available at run time. In this way, you can use the convenience brought by mixin, although you need to define some placeholder attributes in advance.
// Disposable isDisposed: boolean = false; dispose: () => void; // Activatable isActive: boolean = false; activate: () => void; deactivate: () => void;
Finally, mix mixins into the defined classes to complete all the implementation parts.
applyMixins(SmartObject, [Disposable, Activatable]);
Finally, create this help function to help us do the blending operation. It will traverse all the attributes on mixins and copy them to the target, replacing the previous placeholder attributes with the real implementation code.
function applyMixins(derivedCtor: any, baseCtors: any[]) { baseCtors.forEach(baseCtor => { Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => { derivedCtor.prototype[name] = baseCtor.prototype[name]; }) }); }