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(); // 输出:游泳中
叠加顺序等于优先级:后包裹的混入会覆盖先前的同名方法,规划混入顺序时要留意。
混入的注意点
- 混入之间需要互相调用方法时,
this的类型最好显式声明,避免类型信息丢失。 - 混入内可以访问基类的成员,适合做「增强型装饰」,不适合承载复杂状态。
- 有清晰的层次关系时优先用继承;只有行为组合需求时才用混入,两者并不冲突。
小结:混入用「接收基类、返回子类」的工厂函数把多份能力组合进一个类,弥补单继承的不足;它类型安全、可复用,是 TypeScript 中实现组合复用的利器。