Skip to content
This repository was archived by the owner on Oct 12, 2022. It is now read-only.

Mixins: switch to ECMAscript 2017 mixin syntax #918

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 43 additions & 96 deletions pages/Mixins.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,132 +8,79 @@ You may be familiar with the idea of mixins or traits for languages like Scala,
In the code below, we show how you can model mixins in TypeScript.
After the code, we'll break down how it works.

__Please note:__ this sample uses ECMAScript 2017 syntax that was to Typescript in version 2.2. It will not work in older versions.

```ts
type Constructor<T> = new (...args: any[]) => T;

// Disposable Mixin
class Disposable {
isDisposed: boolean;
function Disposable<T extends Constructor<{}>>(SuperClass: T) {
return class extends SuperClass {
isDisposed = false;
dispose() {
this.isDisposed = true;
this.isDisposed = true;
}

};
}

// Activatable Mixin
class Activatable {
isActive: boolean;
function Activatable<T extends Constructor<{}>>(SuperClass: T) {
return class extends SuperClass {
isActive = false;
activate() {
this.isActive = true;
this.isActive = true;
}
deactivate() {
this.isActive = false;
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;
class SmartObject extends Disposable(Activatable(Object)) {
constructor() {
super();
setInterval(
() => console.log(this.isActive + ' : ' + this.isDisposed),
500,
);
}

interact() {
this.activate();
}
}
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];
});
});
}
```

# Understanding the sample
The code sample starts with the Constructor type.
This is a helper type that specifies what a constructor looks like.

The code sample starts with the two classes that will act as our mixins.
```ts
type Constructor<T> = new (...args: any[]) => T;
```

Next the two mixins are defined. They are functions that return a class with the functionality we want to add.
You can see each one is focused on a particular activity or capability.
We'll later mix these together to form a new class from both capabilities.
The `Constructor` type is used to constrain what kind of super class the mixin can enhance, in this case it's simply set to the Object type: `{}`.

```ts
// Disposable Mixin
class Disposable {
isDisposed: boolean;
dispose() {
this.isDisposed = true;
}

}

// Activatable Mixin
class Activatable {
isActive: boolean;
activate() {
this.isActive = true;
}
deactivate() {
this.isActive = false;
}
function Disposable<T extends Constructor<{}>>(SuperClass: T) {
return class extends SuperClass {
// ...
};
}
```

Next, we'll create the class that will handle the combination of the two mixins.
Let's look at this in more detail to see how it does this:

```ts
class SmartObject implements Disposable, Activatable {
```

The first thing you may notice in the above is that instead of using `extends`, we use `implements`.
This treats the classes as interfaces, and only uses the types behind Disposable and Activatable rather than the implementation.
This means that we'll have to provide the implementation in class.
Except, that's exactly what we want to avoid by using mixins.

To satisfy this requirement, we create stand-in properties and their types for the members that will come from our mixins.
This satisfies the compiler that these members will be available at runtime.
This lets us still get the benefit of the mixins, albeit with some bookkeeping overhead.
The mixins are applied using the `extends` clause. You'll notice we're using `Object` as a base class, but you can extend any base class you like.
The mixins are applied on top of the base class by calling their functions with the base class as a parameter.

```ts
// Disposable
isDisposed: boolean = false;
dispose: () => void;
// Activatable
isActive: boolean = false;
activate: () => void;
deactivate: () => void;
class SmartObject extends Disposable(Activatable(Object)) {
```

Finally, we mix our mixins into the class, creating the full implementation.

```ts
applyMixins(SmartObject, [Disposable, Activatable]);
```

Lastly, we create a helper function that will do the mixing for us.
This will run through the properties of each of the mixins and copy them over to the target of the mixins, filling out the stand-in properties with their implementations.

```ts
function applyMixins(derivedCtor: any, baseCtors: any[]) {
baseCtors.forEach(baseCtor => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
derivedCtor.prototype[name] = baseCtor.prototype[name];
});
});
}

```
Finally, we instantiate our `SmartObject` and see that the mixin's properties and methods are available as expected.