TypeScript 混入

JavaScript 的类只能单继承,但一个对象常常需要同时具备多份能力,例如既「会飞」又「会游泳」。混入(Mixin)是一种把多个类的能力组合进一个类的设计技巧:不复制代码、不层层继承,而是通过函数与类型运算把能力「拼装」进去,实现组合优于继承。

混入要解决的问题

假设已有多个功能类,每个都提供了独立方法:

class Flyer {
  fly(): void {
    console.log("飞行中");
  }
}
class Swimmer {
  swim(): void {
    console.log("游泳中");
  }
}

要让一个 Duck 同时拥有 fly 和 swim,单继承只能二选一,复制代码又会造成重复维护。混入正是为这种「多份能力组合」而生。

泛型工厂函数:标准混入写法

核心思路是写一个函数:接收基类构造函数,返回扩展后的子类

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

function Flyable<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    fly(): void {
      console.log("飞行中");
    }
  };
}

class Animal {
  constructor(public name: string) {}
}

class Bird extends Flyable(Animal) {}
const bird = new Bird("燕子");
console.log(bird.name); // 输出:燕子
bird.fly();             // 输出:飞行中

返回的匿名子类完整保留新方法,TypeScript 也能自动推断出 Bird 的全部成员,类型零损失。

叠加多个混入

混入函数可以层层包裹,一次组合多份能力(沿用上面的 Constructor 与 Flyable 定义即可运行):

function Swimmable<TBase extends Constructor>(Base: TBase) {
  return class extends Base {
    swim(): void {
      console.log("游泳中");
    }
  };
}

class Duck extends Swimmable(Flyable(Animal)) {}
const duck = new Duck("唐老鸭");
duck.fly();  // 输出:飞行中
duck.swim(); // 输出:游泳中

叠加顺序等于优先级:后包裹的混入会覆盖先前的同名方法,规划混入顺序时要留意。

混入的注意点

  1. 混入之间需要互相调用方法时,this 的类型最好显式声明,避免类型信息丢失。
  2. 混入内可以访问基类的成员,适合做「增强型装饰」,不适合承载复杂状态。
  3. 有清晰的层次关系时优先用继承;只有行为组合需求时才用混入,两者并不冲突。

小结:混入用「接收基类、返回子类」的工厂函数把多份能力组合进一个类,弥补单继承的不足;它类型安全、可复用,是 TypeScript 中实现组合复用的利器。

笔记加载中…