YouTip LogoYouTip

Ts Mixin

Mixin is a code reuse pattern used to mix multiple independent functional modules into a single class. TypeScript implements Mixin through generic functions that return extended classes, compensating for the inability of single inheritance to reuse behaviors from multiple sources. * * * ## Basic Concepts The core of Mixin is a generic function that receives a base class and returns an extended class. The `Constructor` type constraint is used to describe "any class that can be inherited". ## Example // Constructor type: describes any instantiable class type Constructor=new(...args: any[])=> T; // Mixin function: receives a base class, returns an extended new class function Timestamped(Base: TBase){ return class extends Base { createdAt =new Date(); }; } // Base class class User { constructor(public name: string){} } // 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:** Alicetrue > **Note:** Mixin does not modify the original class, but returns a completely new extended class. The original `User` class is not affected. * * * ## Combining Multiple Mixins By nesting multiple Mixin function calls, multiple capabilities can be stacked onto the same class in sequence. ## Example type Constructor=new(...args: any[])=> T; // Mixin 1: adds timestamp function Timestamped(Base: TBase){ return class extends Base { createdAt =new Date(); }; } // Mixin 2: adds serialization function Serializable(Base: TBase){ return class extends Base { serialize(): string { return JSON.stringify(this); } }; } // Mixin 3: adds logging (depends on serialize method) function Loggable<TBase extends Constructor>(Base: TBase){ return class extends Base { log():void{ console.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:** {"name":"Phone","price":999,"createdAt":"2026-..."}true * * * ## Combining Mixin with Interface The class returned by 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=new(...args: any[])=> T; // Define capability interfaces interface ISerializable { serialize(): string; } interface ICloneable{ clone(): T; } // Mixin implements interface function Serializable(Base: TBase){ return class extends Base implements ISerializable { serialize(): string { return JSON.stringify(this); } }; } function Cloneable(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 Getting Started","Main Content..."); const a2 = a1.clone(); a2.title="TypeScript Advanced"; console.log(a1.serialize()); // {"title":"TypeScript Getting Started","content":"Main Content..."} console.log(a2.serialize()); // {"title":"TypeScript Advanced","content":"Main Content..."} console.log(a1.title=== a2.title);// false(Modify independently after cloning) **Output:** {"title":"TypeScript Getting Started","content":"Main Content..."}{"title":"TypeScript Advanced","content":"Main Content..."}false * * * ## Constrained Mixin Through generic constraints, Mixin can be limited to only mix into classes that meet specific conditions, avoiding missing necessary properties at runtime. ## Example type Constructor=new(...args: any[])=> T; // Constraint: base class must have id and name properties type WithIdAndName = Constructor; function Printable(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();// Keyboard // Error example (compiler will block): // class NoId { constructor(public name: string) {} } // const Bad = Printable(NoId); // Error: NoId is missing id property **Output:** Keyboard * * * ## Mixin vs Inheritance Comparison | Dimension | Inheritance (extends) | Mixin | | --- | --- | --- | | Number of Sources | Can only inherit one parent class | Can stack any number | | Coupling Degree | Strong coupling between subclass and parent class | Each Mixin is independent, low coupling | | Reuse Granularity | Reuses entire class capabilities | Reuses single functionality on demand | | Type Safety | Natively supported | Requires generic constraints to ensure | | Applicable Scenarios | Strong "is-a" relationship | Cross-cutting concerns (logging, serialization, caching, etc.) | * * * ## Summary * **Core Pattern:** Mixin is a generic function that receives a base class and returns an extended class. `Constructor` is the standard constraint type * **Capability Stacking:** Nesting multiple Mixin function calls allows combining multiple capabilities into the same class * **Interface Combination:** Classes returned by Mixin can implement interfaces. Consumers only depend on interfaces, not concrete classes * **Generic Constraints:** By constraining `TBase`, the applicable scope of Mixin can be limited, preventing incorrect usage at compile time * **Applicable Scenarios:** Cross-cutting concerns such as logging, serialization, cloning, timestamps, etc., superior to multi-level inheritance
← Ts Type GuardsTs Abstract Class β†’