TypeScript Mixin

A Mixin is a code reuse pattern used to mix multiple independent functional modules into a single class.

TypeScript implements Mixins by using generic functions to return extended classes, compensating for the limitation that single inheritance cannot reuse behavior from multiple sources.

SVG Diagram: Mixin Composition Process Background Title Mixin Composition Process Base Class User User Class Arrow to Mixin1 SerializableMixin + serialize() Arrow to Mixin2 TimestampedMixin + timestamp Arrow to Result Composition Result Extended Class Explanation Below How Mixins Work Principle Explanation 1 1. A Mixin is a function Takes a class and returns a new extended class Principle Explanation 2 2. Inheritance rather than modification Inherit through extends, without modifying the original class Principle Explanation 3 3. Stackable composition Multiple Mixins can be nested and combined Arrow labels

Basic Concepts

The core of a Mixin is a generic function that takes a base class and returns an extended class.ConstructorThe type constraint is used to describe "any class that can be inherited."

Example

// Constructor type: describes any instantiable class
type Constructor<T = {}> = new (...args: any[]) => T;

// Mixin function: takes a base class and returns a new extended class
function Timestamped<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        createdAt = new Date();
    };
}

// Base class
class User {
    constructor(public name: string) {}
}

// Get a new class after mixing in
const TimestampedUser = Timestamped(User);
const user = new TimestampedUser("Alice");

console.log(user.name);                        // Alice
console.log(user.createdAt instanceof Date);   // true

Output:

Alice
true

Note:A Mixin does not modify the original class; it returns a brand new extended class. The originalUserclass is not affected.


Combining Multiple Mixins

Nesting multiple Mixin functions lets you stack multiple capabilities onto the same class in sequence.

Example

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

// Mixin 1: Add timestamp
function Timestamped<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        createdAt = new Date();
    };
}

// Mixin 2: Add serialization
function Serializable<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        serialize(): string {
            return JSON.stringify(this);
        }
    };
}

// Mixin 3: Add logging (depends on the serialize method)
function Loggable<TBase extends Constructor<{ serialize(): string }>>(Base: TBase) {
    return class extends Base {
        log(): void {
            console.log("[LOG]", this.serialize());
        }
    };
}

class Product {
    constructor(public name: string, public price: number) {}
}

// Stack three Mixins in sequence
const AdvancedProduct = Loggable(Serializable(Timestamped(Product)));

const p = new AdvancedProduct("Phone", 999);
p.log();
console.log(p.createdAt instanceof Date);  // true

Output:

[LOG] {"name":"Phone","price":999,"createdAt":"2026-..."}
true

Mixin and Interfaces

The class returned by a Mixin can declare that it implements an interface, allowing consumers to operate on the mixed object through the interface type without depending on the concrete implementation.

Example

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

// Define a capability interface
interface ISerializable {
    serialize(): string;
}

interface ICloneable<T> {
    clone(): T;
}

// Mixin implements the interface
function Serializable<TBase extends Constructor>(Base: TBase) {
    return class extends Base implements ISerializable {
        serialize(): string {
            return JSON.stringify(this);
        }
    };
}

function Cloneable<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        clone() {
            return Object.assign(
                Object.create(Object.getPrototypeOf(this)),
                this
            );
        }
    };
}

class Article {
    constructor(public title: string, public content: string) {}
}

const RichArticle = Cloneable(Serializable(Article));

const a1 = new RichArticle("TypeScript Introduction", "Content...");
const a2 = a1.clone();
a2.title = "TypeScript Advanced";

console.log(a1.serialize());
// {"title":"TypeScript Introduction","content":"Content..."}

console.log(a2.serialize());
// {"title":"TypeScript Advanced","content":"Content..."}

console.log(a1.title === a2.title);  // false (independently modified after cloning)

Output:

{"title":"TypeScript 入门","content":"正文内容..."}
{"title":"TypeScript 进阶","content":"正文内容..."}
false

Constrained Mixin

Using generic constraints, you can restrict a Mixin to only mix into classes that meet specific conditions, avoiding missing required properties at runtime.

Example

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

// Constraint: the base class must have id and name properties
type WithIdAndName = Constructor<{ id: number; name: string }>;

function Printable<TBase extends WithIdAndName>(Base: TBase) {
    return class extends Base {
        print(): void {
            console.log(`[${this.id}] ${this.name}`);
        }
    };
}

class Item {
    constructor(public id: number, public name: string) {}
}

// Correct: Item satisfies the constraint
const PrintableItem = Printable(Item);
const item = new PrintableItem(42, "Keyboard");
item.print();  // [42] Keyboard

// Error example (the compiler will prevent it):
// class NoId { constructor(public name: string) {} }
// const Bad = Printable(NoId); // Error: NoId lacks the id property

Output:

[42] Keyboard

Mixin vs Inheritance

Dimension Inheritance (extends) Mixin
Number of sourcesCan only inherit one parent classCan stack any number
Coupling degreeStrong coupling between subclass and parent classEach Mixin is independent, low coupling
Reuse granularityReuse the capabilities of the entire classReuse a single feature on demand
Type safetyNative supportNeeds to be ensured via generic constraints
Applicable scenariosStrong "is-a" relationshipCross-cutting concerns (logging, serialization, caching, etc.)

Summary

  • Core pattern:A Mixin is a generic function that takes a base class and returns an extended class,Constructor<T>is the standard constraint type
  • Capability stacking:Nesting multiple Mixin functions allows combining multiple capabilities into the same class
  • Interface integration:The class returned by a Mixin can implement interfaces; consumers only need to depend on interfaces rather than concrete classes
  • Generic constraints:Through constraints,TBaseyou can limit the applicable scope of a Mixin and prevent incorrect usage at compile time
  • Applicable scenarios:Logging, serialization, cloning, timestamps and other cross-cutting concerns; better than multi-level inheritance
Other extensions