Skip to Content
Course content

201: Mixin Classes Explained

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've probably hit a wall where you want a class to do two different things, but TypeScript (and JavaScript) only lets you extend one single class. I see this all the time when people are building game engines or complex UI components.

// The "Dream" Code (which doesn't actually work)
class Entity {
  id: string = Math.random().toString();
}

class Positionable {
  x: number = 0;
  y: number = 0;
  move(dx: number, dy: number) { this.x += dx; this.y += dy; }
}

class Damageable {
  hp: number = 100;
  takeDamage(amt: number) { this.hp -= amt; }
}

// ❌ Error: Classes can only extend a single class.
class Player extends Entity, Positionable, Damageable {
  name: string = "Hero";
}

The Multiple Inheritance Wall

The code above is a classic mistake. You're trying to use multiple inheritance, which exists in languages like C++, but is a hard "no" in the JavaScript ecosystem. You might be tempted to just use interfaces here, but interfaces don't provide implementation. If you use an interface for Damageable, you'll find yourself copy-pasting the takeDamage logic into every single class that needs it. That's a maintenance nightmare waiting to happen.

So, how do we share behavior across unrelated classes without creating a giant, fragile inheritance tree? We use Mixins.

The Class Factory Pattern

In TypeScript, a Mixin isn't a special keyword; it's just a function that takes a class and returns a new class that extends the one it was given. Think of it as a "class factory."

To make this work with TypeScript's type system, we first need a helper type to describe what a "constructable" class looks like. I usually stick this in a utilities file:

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

Now, instead of defining Positionable as a class, we define it as a function that wraps a base class:

function PositionableMixin<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    x = 0;
    y = 0;
    move(dx: number, dy: number) {
      this.x += dx;
      this.y += dy;
      console.log(`Moved to ${this.x}, ${this.y}`);
    }
  };
}

Stacking Behaviors

The magic happens when you compose these mixins. Because each mixin returns a class, you can nest them. It looks a bit weird at first—like an onion—but it's incredibly powerful. You're essentially building a custom class hierarchy on the fly.

function DamageableMixin<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    hp = 100;
    takeDamage(amt: number) {
      this.hp -= amt;
      console.log(`Took ${amt} damage. HP now: ${this.hp}`);
    }
  };
}

// Now we compose them. 
// We start with Entity, wrap it in Positionable, then wrap that in Damageable.
class Player extends DamageableMixin(PositionableMixin(Entity)) {
  name = "Hero";
}

const player = new Player();
player.move(10, 5);    // From PositionableMixin
player.takeDamage(20); // From DamageableMixin
console.log(player.id); // From Entity

I'll be honest: the syntax DamageableMixin(PositionableMixin(Entity)) can get ugly if you have six or seven mixins. But it solves the fundamental problem. You've decoupled the "ability to move" from the "ability to take damage," and you can now apply those abilities to any class—be it a Player, a DestructibleWall, or a FlyingNPC—without worrying about where they fit in a rigid tree.




📋 Practical Task

Exercise: Building a Plugin-based Logging System with Mixins

You are building a logging system. You have a base Logger class that simply prints a message to the console. However, different environments need different "plugins" (capabilities) added to the logger without rewriting the base logic.

Your Task:

  1. Create a base class Logger with a method log(message: string): void that prints the message.
  2. Implement a TimestampMixin that overrides the log method to prepend the current ISO date string to the message.
  3. Implement a ColorizeMixin that overrides the log method to wrap the message in ANSI color codes (e.g., \x1b[32m${message}\x1b[0m for green).
  4. Create a class EnhancedLogger that uses both mixins.
  5. Instantiate EnhancedLogger and call log("System initialized"). The output should be both timestamped and colored.

Hint: Remember that the order of mixins matters! The last mixin wrapped is the first one to intercept the method call.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.