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 a walk method
  • Runner, which exposes a run method
  • Athlete, 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.

👋 Hey, I'm Vlad Mihet
I'm Vlad Mihet, a blogger & Full-Stack Engineer who loves teaching others and helping small businesses develop and improve their technical solutions & digital presence.

💬 Leave a comment

Your email address will not be published. Required fields are marked *

We will never share your email with anyone else.