DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 963,673 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Cover image for πŸŒ€ Mixins in Typescript πŸ€
Aravind V
Aravind V

Posted on • Updated on • Originally published at devpost.hashnode.dev

πŸŒ€ Mixins in Typescript πŸ€

Mixins is a popular way of building up classes from reusable components by combining simpler partial classes.

In this article we are trying to demonstrate how we can use them in typescript.

Identify the Base Class πŸ’«

We will start this by creating a base class like the below one:


class Book {
  name = "";
  constructor(name: string) {
    this.name = name;
  }
}

Enter fullscreen mode Exit fullscreen mode

Define a type definition focusing on our base class ⚑

Define a type definition which is used to declare that the type being passed, is nothing but a typical class.


type Constructor = new (...args: any[]) => {};

Enter fullscreen mode Exit fullscreen mode

Class expression way to define a mixin 🌿

Define the factory function which will return a class expression, this function is what we call Mixin here.



function Pages1<TBase extends Ctr>(Base: TBase) {
    return class Pages extends Base {
      _pages = 1;
      setPages(pages: number) {
        this._pages = pages;
      }
      get Pages(): number {
        return this._pages;
      }
    };
  }

Enter fullscreen mode Exit fullscreen mode

Time to use the mixin to derive classes βœ‚οΈ

Let us use this newly created mixin to create a new classes as follows:


    const PagedBook = Pages1(Book);
    const HP = new PagedBook("Harry Potter and the Sorcerer's Stone");
    HP.setPages(223);
    console.log(`${HP.name} - ${HP.Pages}`);

    const AW = new PagedBook("Alice's Adventures in Wonderland");
    AW.setPages(353);
    console.log(`${AW.name} - ${AW.Pages}`);

Enter fullscreen mode Exit fullscreen mode

In the above example, this many sound weird at first sight that the same could be easily defined in the earlier base class itself, but what we have achieved is that we are able to generate new subclass by combining partial class at runtime, based on our requirement. And hence it is powerful.

Constrained Mixins πŸ”­

We can also make our Ctr type defined earlier more generic by using the below changes.


type GenCtr<T = {}> = new (...args: any[]) => T;

type BookCtr = GenCtr<Book>;

Enter fullscreen mode Exit fullscreen mode

But why do we need to use this new generic constructor, this is to make sure we can constrain by choosing the right base class features before we can extend with our mixin.


function Pages2<TBase extends BookCtr>(Base: TBase) {
    return class Pages extends Base {
      _pages = 1;
      setPages(pages: number) {
        this._pages = pages;
      }
      get Pages(): number {
        return this._pages;
      }
    };
  }

Enter fullscreen mode Exit fullscreen mode

The above works the same way as the previous mixin, but we have just demonstrated the use of constraints by using mixins to build classes.


    const PagedBook = Pages2(Book);
    const HP = new PagedBook("Harry Potter and the Sorcerer's Stone");
    HP.setPages(223);
    console.log(`${HP.name} - ${HP.Pages}`);

    const AW = new PagedBook("Alice's Adventures in Wonderland");
    AW.setPages(353);
    console.log(`${AW.name} - ${AW.Pages}`);

Enter fullscreen mode Exit fullscreen mode

Another example πŸ”†

Another example to add more notes by what we just meant.


type AuthorCtr = GenCtr<{ setAuthor: (author: string) => void }>;

function AuthoredBook<TBase extends AuthorCtr>(Base: TBase) {
  return class AuthoredBook extends Base {
    Author(name: string) {
        this.setAuthor(name) 
    }
  };
}

Enter fullscreen mode Exit fullscreen mode

In the segment above we have created a type which expects the base class to have a method setAuthor which takes a param author so that the mixin could be applied to extend the base classes. This is one of the ways to create a constrained mixin.

Why do we need to add constraints ❓

  • we can identity the constraint, it will help us write the mixin easily targeting the required features with the right set of dependancy at the same time.
  • second one this is that typescript we do this everywhere making well defined types, so that we are may easily understand the block at the same time tsc will always remind us when we commit error, besides giving us inference while coding.

This also let us define abstract mixins which are loosely coupled targeting only the specific features and can be chainned as per the necessity as in the below example.


class Novel {
    _name = "";
    _author= "";
    constructor(name: string) {
      this._name = name;
    }
    setAuthor(author: string){
        this._author=author;
    }
    about():string {
        return `${this._name} by ${this._author}`
    }
  }

Enter fullscreen mode Exit fullscreen mode

The above code snippet used a raw Novel class, here we can do some mixins to achieve the desirable effects.

First let us define them as follows;

type Authorable = GenCtr<{ setAuthor: (author: string) => void }>;

function AuthoredBook<TBase extends Authorable>(Base: TBase) {
  return class AuthoredBook extends Base {
    author(fname: string, lname: string) {
        this.setAuthor(`${fname} ${lname}`) 
    }
  };
}

type Printable = GenCtr<{ about: () => string }>;

function PrintBook<TBase extends Printable>(Base: TBase) {
  return class PrintBook extends Base {
    print() {
       return `*****   `+this.about()+`   ******` 
    }
  };
}

Enter fullscreen mode Exit fullscreen mode

In the above code snippet we defined couple of mixins, which is loosely coupled with any base class as it only expect specific methods to mix &enhance it.


const StoryBook1 = AuthoredBook(Novel);
const PrintableBook1 = PrintBook(StoryBook1);

const Sb1 = new PrintableBook1("Gulliver’s Travel");
Sb1.author("Jonathan", "Swift");

console.log(Sb1.print());


Enter fullscreen mode Exit fullscreen mode

πŸ‘‘ What is cool about using mixins it that the order in which the chaining occurs is not important, still the results will be consistent since the partial class features mix one after other as they are applied.


const PrintableBook2 = PrintBook(Novel);
const StoryBook2 = AuthoredBook(PrintableBook2);


const Sb2 = new StoryBook2("Gulliver’s Travel");
Sb2.author("Jonathan", "Swift");

console.log(Sb1.print());

Enter fullscreen mode Exit fullscreen mode

πŸ” Original post at πŸ”— Dev Post

Thanks for supporting! πŸ™

Would be really great if you like to β˜• Buy Me a Coffee, to help boost my efforts.

Buy Me a Coffee at ko-fi.com

Top comments (0)

🀯

"I made 10x faster JSON.stringify() functions, even type safe"

☝️ Must read for JS devs