TypeScript-Mixin

Ein Mixin ist ein Muster zur Code-Wiederverwendung, mit dem mehrere unabhängige Funktionsmodule in eine einzelne Klasse gemischt werden.

TypeScript implementiert Mixins, indem generische Funktionen verwendet werden, um erweiterte Klassen zurückzugeben, was die Einschränkung ausgleicht, dass Einfachvererbung Verhalten aus mehreren Quellen nicht wiederverwenden kann.

SVG-Diagramm: Mixin-Kompositionsprozess 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

Grundlegende Konzepte

Der Kern eines Mixins ist eine generische Funktion, die eine Basisklasse entgegennimmt und eine erweiterte Klasse zurückgibt.ConstructorDie Typbeschränkung wird verwendet, um "jede Klasse, die vererbt werden kann" zu beschreiben.

Beispiel

// Konstruktortyp: beschreibt jede instanziierbare Klasse
type Constructor<T = {}> = new (...args: any[]) => T;

// Mixin-Funktion: nimmt eine Basisklasse entgegen und gibt eine neue erweiterte Klasse zurück
function Timestamped<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        createdAt = new Date();
    };
}

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

// Neue Klasse nach dem Einmischen abrufen
const TimestampedUser = Timestamped(User);
const user = new TimestampedUser("Alice");

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

Ausgabe:

Alice
true

Hinweis:Ein Mixin ändert die ursprüngliche Klasse nicht; es gibt eine brandneue erweiterte Klasse zurück. Die ursprünglicheUserKlasse ist nicht betroffen.


Mehrere Mixins kombinieren

Das Verschachteln mehrerer Mixin-Funktionen ermöglicht es, mehrere Fähigkeiten nacheinander auf dieselbe Klasse zu stapeln.

Beispiel

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

// Mixin 1: Zeitstempel hinzufügen
function Timestamped<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        createdAt = new Date();
    };
}

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

// Mixin 3: Protokollierung hinzufügen (hängt von der serialize-Methode ab)
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) {}
}

// Drei Mixins nacheinander stapeln
const AdvancedProduct = Loggable(Serializable(Timestamped(Product)));

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

Ausgabe:

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

Mixin und Schnittstellen

Die von einem Mixin zurückgegebene Klasse kann deklarieren, dass sie eine Schnittstelle implementiert, wodurch Konsumenten mit dem gemischten Objekt über den Schnittstellentyp arbeiten können, ohne von der konkreten Implementierung abhängig zu sein.

Beispiel

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

// Eine Fähigkeits-Schnittstelle definieren
interface ISerializable {
    serialize(): string;
}

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

// Mixin implementiert das 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 Einführung", "Inhalt...");
const a2 = a1.clone();
a2.title = "TypeScript für Fortgeschrittene";

console.log(a1.serialize());
// {"title":"TypeScript-Einführung","content":"Inhalt..."}

console.log(a2.serialize());
// {"title":"TypeScript für Fortgeschrittene","content":"Inhalt..."}

console.log(a1.title === a2.title);  // false (nach dem Klonen unabhängig geändert)

Ausgabe:

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

Eingeschränktes Mixin

Mithilfe generischer Einschränkungen können Sie ein Mixin darauf beschränken, nur in Klassen eingemischt zu werden, die bestimmte Bedingungen erfüllen, wodurch fehlende erforderliche Eigenschaften zur Laufzeit vermieden werden.

Beispiel

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

// Einschränkung: Die Basisklasse muss die Eigenschaften id und name haben
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) {}
}

// Korrekt: Item erfüllt die Einschränkung
const PrintableItem = Printable(Item);
const item = new PrintableItem(42, "Keyboard");
item.print();  // [42] Keyboard

// Fehlerbeispiel (der Compiler wird es verhindern):
// class NoId { constructor(public name: string) {} }
// const Bad = Printable(NoId); // Fehler: NoId fehlt die id-Eigenschaft

Ausgabe:

[42] Keyboard

Mixin vs. Vererbung

Dimension Vererbung (extends) Mixin
Anzahl der QuellenKann nur von einer Elternklasse erbenKann beliebig viele stapeln
KopplungsgradStarke Kopplung zwischen Unterklasse und ElternklasseJedes Mixin ist unabhängig, geringe Kopplung
WiederverwendungsgranularitätWiederverwendung der Fähigkeiten der gesamten KlasseWiederverwendung einer einzelnen Funktion bei Bedarf
TypsicherheitNative UnterstützungMuss über generische Einschränkungen sichergestellt werden
Anwendbare SzenarienStarke "is-a"-BeziehungQuerschnittsbelange (Logging, Serialisierung, Caching usw.)

Zusammenfassung

  • Kernmuster:Ein Mixin ist eine generische Funktion, die eine Basisklasse entgegennimmt und eine erweiterte Klasse zurückgibt,Constructor<T>ist der Standard-Constraint-Typ
  • Fähigkeits-Stapelung:Das Verschachteln mehrerer Mixin-Funktionen ermöglicht es, mehrere Fähigkeiten in derselben Klasse zu kombinieren
  • Interface-Integration:Die von einem Mixin zurückgegebene Klasse kann Interfaces implementieren; Konsumenten müssen nur von Interfaces abhängen, nicht von konkreten Klassen
  • Generische Constraints:Durch Constraints,TBasekann man den anwendbaren Bereich eines Mixins einschränken und eine falsche Verwendung zur Kompilierungszeit verhindern
  • Anwendbare Szenarien:Protokollierung, Serialisierung, Klonen, Zeitstempel und andere querschnittliche Belange; besser als mehrstufige Vererbung
Weitere Erweiterungen