TypeScript learning - 20 Mixins

Posted by Kingw on Mon, 07 Feb 2022 06:52:09 +0100

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];
        })
    });
}

Topics: Javascript Front-end TypeScript