TypeScript Mixin

Un Mixin es un patrón de reutilización de código que se utiliza para incorporar múltiples módulos de funcionalidad independientes en una clase.

TypeScript implementa Mixins mediante funciones genéricas que devuelven clases extendidas, lo que compensa la limitación de la herencia única de no poder reutilizar comportamientos de múltiples fuentes.

Diagrama SVG: proceso de composición de Mixin Fondo Título Proceso de composición de Mixin Clase base User Clase User Flecha a Mixin1 SerializableMixin + serialize() Flecha a Mixin2 TimestampedMixin + timestamp Flecha al resultado Resultado de la composición Clase extendida Explicación inferior Cómo funciona Mixin Explicación del principio 1 1. Mixin es una función Recibe una clase y devuelve una nueva clase extendida Explicación del principio 2 2. Herencia en lugar de modificación Here da mediante extends, sin modificar la clase original Explicación del principio 3 3. Composición apilable Se pueden combinar múltiples Mixins de forma anidada Marcador de flecha

Conceptos básicos

El núcleo de un Mixin es una función genérica que recibe una clase base y devuelve una clase extendida.ConstructorLa restricción de tipo se utiliza para describir "cualquier clase que pueda ser heredada".

Ejemplo

// Tipo Constructor: describe cualquier clase instanciable
type Constructor<T = {}> = new (...args: any[]) => T;

// Función Mixin: recibe la clase base y devuelve la nueva clase extendida
function Timestamped<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        createdAt = new Date();
    };
}

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

// Tras la mezcla se obtiene una nueva clase
const TimestampedUser = Timestamped(User);
const user = new TimestampedUser("Alice");

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

Resultado de ejecución:

Alice
true

Nota:Mixin no modifica la clase original, sino que devuelve una nueva clase extendida; la claseUseroriginal no se ve afectada.


Combinar múltiples Mixins

Al invocar varias funciones Mixin de forma anidada, se pueden superponer múltiples capacidades en la misma clase.

Ejemplo

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

// Mixin 1: agrega marca de tiempo
function Timestamped<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        createdAt = new Date();
    };
}

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

// Mixin 3: agrega registro (depende del método serialize)
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) {}
}

// Superpone los tres Mixins en orden
const AdvancedProduct = Loggable(Serializable(Timestamped(Product)));

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

Resultado de ejecución:

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

Combinación de Mixin con interfaces

La clase devuelta por un Mixin puede declarar que implementa una interfaz, permitiendo al consumidor operar con el objeto mezclado a través del tipo de interfaz, sin depender de la implementación concreta.

Ejemplo

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

// Define la interfaz de capacidad
interface ISerializable {
    serialize(): string;
}

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

// Mixin implementa la interfaz
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("Introducción a TypeScript", "Contenido del cuerpo...");
const a2 = a1.clone();
a2.title = "TypeScript avanzado";

console.log(a1.serialize());
// {"title":"Introducción a TypeScript","content":"Contenido del cuerpo..."}

console.log(a2.serialize());
// {"title":"TypeScript avanzado","content":"Contenido del cuerpo..."}

console.log(a1.title === a2.title);  // false (modificación independiente tras la clonación)

Resultado de ejecución:

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

Mixin con restricciones

Mediante restricciones genéricas, se puede limitar el Mixin para que solo se aplique a clases que cumplan condiciones específicas, evitando la falta de propiedades necesarias en tiempo de ejecución.

Ejemplo

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

// Restricción: la clase base debe tener las propiedades id y name
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) {}
}

// Correcto: Item cumple la restricción
const PrintableItem = Printable(Item);
const item = new PrintableItem(42, "Keyboard");
item.print();  // [42] Keyboard

// Ejemplo incorrecto (el compilador lo impedirá):
// class NoId { constructor(public name: string) {} }
// const Bad = Printable(NoId); // Error: a NoId le falta la propiedad id

Resultado de ejecución:

[42] Keyboard

Comparación entre Mixin y herencia

Dimensión Herencia (extends) Mixin
Cantidad de fuentesSolo puede heredar de una clase padreSe pueden superponer cualquier cantidad
Grado de acoplamientoFuerte acoplamiento entre subclase y clase padreCada Mixin es independiente, bajo acoplamiento
Granularidad de reutilizaciónReutiliza la capacidad de toda la claseReutiliza una única función según necesidad
Seguridad de tiposSoporte nativoDebe garantizarse mediante restricciones genéricas
Casos de usoRelación "is-a" fuerteAspectos transversales (registro, serialización, caché, etc.)

Resumen

  • Patrón central:Mixin es una función genérica que recibe una clase base y devuelve una clase extendida,Constructor<T>es el tipo de restricción estándar
  • Superposición de capacidades:Al invocar de forma anidada varias funciones Mixin, se pueden combinar múltiples capacidades en la misma clase
  • Combinación con interfaces:La clase devuelta por Mixin puede implementar interfaces; el consumidor solo necesita depender de la interfaz en lugar de la clase concreta
  • Restricciones genéricas:Mediante restriccionesTBasese puede limitar el ámbito de aplicación del Mixin y evitar usos incorrectos en tiempo de compilación
  • Casos de uso:Aspectos transversales como registro, serialización, clonación, marcas de tiempo, etc., superiores a la herencia multicapa
Otras extensiones