DEV Community

Cover image for Mixins in typescript
Pratik sharma
Pratik sharma Subscriber

Posted on • Originally published at blog.coolhead.in

Mixins in typescript

A mixin is an abstract subclass; i.e. a subclass definition that may be applied to different superclasses to create a related family of modified classes.

Gilad Bracha and William Cook, Mixin-based Inheritance

  • mixin definition: The definition of a class that may be applied to different superclasses.

  • mixin application: The application of a mixin definition to a specific superclass, producing a new subclass.

Mixin libraries like Cocktail, traits.js, and patterns described in many blog posts (like one of the latest to hit Hacker News: Using ES7 Decorators as Mixins), generally work by modifying objects in place, copying in properties from mixin objects and overwriting existing properties.

This is often implemented via a function similar to this:

function mixin(source, target) { for (var prop in source) { if (source.hasOwnProperty(prop)) { target[prop] = source[prop]; } } } 
Enter fullscreen mode Exit fullscreen mode

A version of this has even made it into JavaScript as Object.assign.

mixin() is usually then called on a prototype:

mixin(MyMixin, MyClass.prototype); 
Enter fullscreen mode Exit fullscreen mode

and now MyClass has all the properties defined in MyMixin.

functors: function which creates a function

function myloggerFunction() { return (str) => {console.log(str)} } const logger1 = myloggerFunction(); logger1('hello'); 
Enter fullscreen mode Exit fullscreen mode

functions that creates class

function myloggerFunction() { return class MyLoggerClass() { private } } 
Enter fullscreen mode Exit fullscreen mode
function myLogFunction() { return (str: string) => { console.log(str); }; } function myLoggerClass() { return new (class Logger { private completeLog: string = ""; log(str: string) { console.log(str); this.completeLog += `${str}\n`; } dumpLog() { return this.completeLog; } })(); } function SimpleMemoryDatabase<T>() { return class SimpleMemoryDatabase { private db: Record<string, T> = {}; set(id: string, value: T): void { this.db[id] = value; } get(id: string): T { return this.db[id]; } getObject(): Record<string, T> { return this.db; } }; } const StringDatabase = SimpleMemoryDatabase<string>(); const sdb1 = new StringDatabase(); sdb1.set("name", "Jack"); console.log(sdb1.get("name")); type Constructor<T> = new (...args: any[]) => T; function Dumpable< T extends Constructor<{ getObject(): object; }> >(Base: T) { return class Dumpable extends Base { dump() { console.log(this.getObject()); } }; } const DumpableStringDatabase = Dumpable(StringDatabase); const sdb2 = new DumpableStringDatabase(); sdb2.set("name", "Jack"); sdb2.dump(); 
Enter fullscreen mode Exit fullscreen mode

[A mixin is] a function that

  1. takes a constructor,

  2. creates a class that extends that constructor with new functionality

  3. returns the new class

// Needed for all mixins type Constructor<T = {}> = new (...args: any[]) => T; //////////////////// // Example mixins //////////////////// // A mixin that adds a property function Timestamped<TBase extends Constructor>(Base: TBase) { return class extends Base { timestamp = Date.now(); }; } // a mixin that adds a property and methods function Activatable<TBase extends Constructor>(Base: TBase) { return class extends Base { isActivated = false; activate() { this.isActivated = true; } deactivate() { this.isActivated = false; } }; } //////////////////// // Usage to compose classes //////////////////// // Simple class class User { name = ''; } // User that is Timestamped const TimestampedUser = Timestamped(User); // User that is Timestamped and Activatable const TimestampedActivatableUser = Timestamped(Activatable(User)); //////////////////// // Using the composed classes //////////////////// const timestampedUserExample = new TimestampedUser(); console.log(timestampedUserExample.timestamp); const timestampedActivatableUserExample = new TimestampedActivatableUser(); console.log(timestampedActivatableUserExample.timestamp); console.log(timestampedActivatableUserExample.isActivated); 
Enter fullscreen mode Exit fullscreen mode
// Define a simple class with a greet method class Greeter { greet(name: string) { console.log(`Hello, ${name}!`); } } // Define a mixin that adds a log method to a class type Loggable = { log(message: string): void }; function withLogging<T extends new (...args: any[]) => Loggable>(Base: T) { return class extends Base { log(message: string) { console.log(`[${new Date().toISOString()}] ${message}`); } }; } // Create a new class that combines the Greeter and Loggable mixins const MyGreeter = withLogging(Greeter); // Use the new class to create an instance and call its methods const greeter = new MyGreeter(); greeter.greet("Alice"); // Output: "Hello, Alice!" greeter.log("An event occurred."); // Output: "[2023-04-04T12:00:00.000Z] An event occurred." 
Enter fullscreen mode Exit fullscreen mode

This is just the beginning of many topics related to mixins in JavaScript. I'll post more about things like:

  • Enhancing Mixins with Decorator Functions new post that covers:

  • Caching mixin applications so that the same mixin applied to the same superclass reuses a prototype.

  • Getting instanceof to work.

  • How mixin inheritance can address the fear that ES6 classes and classical inheritance are bad for JavaScript.

  • Using subclass-factory-style mixins in ES5.

  • De-duplicating mixins so mixin composition is more usable.

References:

https://bryntum.com/blog/the-mixin-pattern-in-typescript-all-you-need-to-know/

%[https://www.youtube.com/watch?v=LvjNGo5ALyQ]

https://medium.com/@saif.adnan/typescript-mixin-ee962be3224d

https://egghead.io/lessons/typescript-use-the-optional-chaining-operator-in-typescript

https://javascript.info/mixins

Top comments (0)