If you’ve worked with TypeScript extensively, you would know that we cannot inherit or extend from more than one class at a time. However, this inconvenience is something we can get around by using something called Mixins.
So, what exactly are Mixins?
Introduction to TypeScript’s Mixins
As mentioned prior, Mixins allow us to inherit from multiple classes by creating partial classes that we may combine to form a single class that contains all of the properties and methods from those partial classes.
But in order to understand how Mixins work, let’s first understand the problem they are trying to solve.
The Problem With Class Inheritance in TypeScript
Let’s say, for example, that we have three classes:
Person
, which exposes awalk
methodRunner
, which exposes arun
methodAthlete
, which should expose/inherit the methods exposed in the other two classes
The code for the definitions of these classes would look something like this:
class Person {
walk(name: string) {
console.log(`${name} is walking...`);
}
};
class Runner {
run(name: string) {
console.log(`${name} is running...`);
}
};
class Athlete extends Person, Runner {}; // Error: Classes can only extend a single class
Understanding Interface Class Extension and Declaration Merging
There are two concepts within TypeScript that we’ll be taking advantage of when creating a Mixin:
- Interface Class Extension
- Declaration Merging
Interface Class Extension
Unlike classes, interfaces can extend multiple classes in TypeScript. You can read more about interfaces and their default behavior by reading this article.
When an interface extends a class, it does only extend the class members, not their implementation. That’s due to interfaces not containing concrete implementations.
Declaration Merging
When two or more declarations are declared with the same name, TypeScript merges them into one. We’ve already covered this topic in more depth here.
interface A {
propertyOne: string;
propertyTwo: string;
}
interface A {
propertyThree: string;
}
const interfaceImplementation: A = {
propertyOne: '1',
propertyTwo: '2',
propertyThree: '3',
};
Whereas the interfaceImplementation
implementation of our A
interface will contain the properties from all the definitions of the interface.
Implementing Our Mixin Helper Function
Now that we’re a bit more comfortable with the concepts mentioned above, let’s leverage them to create an interface with the same name as the class we’re trying to extend to:
class Athlete {}
interface Athlete extends Person, Runner {}
As a result of Declaration Merging, the Athlete
class will be merged with the Athlete
interface, thus meaning that the Athlete
class will now contain the method definitions of both the Person
and the Runner
classes.
To make our lives easier, we’ll allow the Athlete
class to implement the functions inherited from Car
and Lorry
using a helper function from the TypeScript Official Docs:
function applyMixins(derivedCtor: any, constructors: any[]) {
constructors.forEach((baseCtor) => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
Object.defineProperty(
derivedCtor.prototype,
name,
Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
Object.create(null)
);
});
});
}
The function takes the name of the class to which we want to copy the implementations as the first argument (which, in our case, is Athlete) and takes an array of classes from which we want to copy the implementations as the second argument (which, in our case, are Person
and Runner
).
We will now use the function as such:
applyMixing(Athlete, [Person, Runner]);
And that will allow us to use the methods defined under the two classes we have inherited from just fine:
const athlete = new Athlete();
athlete.walk('James'); // James is walking...
athlete.run('James'); // James is running...
Summary
I hope you’ve enjoyed the read and got a better understanding of what Mixins are, when we would need to use them, and, more importantly, how we would use them in our TypeScript project.
Also to note, interfaces are handy when working with TypeScript inside React; you can check out an article highlighting the creation of a new React project with TypeScript or even the addition of TypeScript to an already existing React project here.
💬 Leave a comment