混入

除了传统的面向对象继承体系之外,另一种通过可复用组件构建类的流行方式,是将较简单的部分类组合起来。你可能熟悉 Scala 等语言中的混入或特质(trait)概念,这种模式在 JavaScript 社区中也已经相当流行。

混入是如何工作的?

这种模式依赖将泛型与类继承结合使用来扩展基类。TypeScript 对混入的最佳支持是通过类表达式模式实现的。你可以在这里进一步了解这种模式在 JavaScript 中的工作方式。

首先,我们需要一个类,混入将应用在它之上:

ts
class Sprite {
name = "";
x = 0;
y = 0;
 
constructor(name: string) {
this.name = name;
}
}
Try

然后你需要一个类型和一个工厂函数,该函数返回一个扩展基类的类表达式。

ts
// To get started, we need a type which we'll use to extend
// other classes from. The main responsibility is to declare
// that the type being passed in is a class.
 
type Constructor = new (...args: any[]) => {};
 
// This mixin adds a scale property, with getters and setters
// for changing it with an encapsulated private property:
 
function Scale<TBase extends Constructor>(Base: TBase) {
return class Scaling extends Base {
// Mixins may not declare private/protected properties
// however, you can use ES2020 private fields
_scale = 1;
 
setScale(scale: number) {
this._scale = scale;
}
 
get scale(): number {
return this._scale;
}
};
}
Try

这些都就绪之后,你就可以创建一个表示应用了混入的基类的类:

ts
// Compose a new class from the Sprite class,
// with the Mixin Scale applier:
const EightBitSprite = Scale(Sprite);
 
const flappySprite = new EightBitSprite("Bird");
flappySprite.setScale(0.8);
console.log(flappySprite.scale);
Try

受约束的混入

在上述形式中,混入对类本身没有底层认知,这可能让你难以实现想要的设计。

为了对此建模,我们修改原来的构造函数类型,使其接受一个泛型参数。

ts
// This was our previous constructor:
type Constructor = new (...args: any[]) => {};
// Now we use a generic version which can apply a constraint on
// the class which this mixin is applied to
type GConstructor<T = {}> = new (...args: any[]) => T;
Try

这样就可以创建仅适用于受约束基类的类:

ts
type Positionable = GConstructor<{ setPos: (x: number, y: number) => void }>;
type Spritable = GConstructor<Sprite>;
type Loggable = GConstructor<{ print: () => void }>;
Try

然后你可以创建仅在拥有特定基类可供继承时才能工作的混入:

ts
function Jumpable<TBase extends Positionable>(Base: TBase) {
return class Jumpable extends Base {
jump() {
// This mixin will only work if it is passed a base
// class which has setPos defined because of the
// Positionable constraint.
this.setPos(0, 20);
}
};
}
Try

替代模式

本文档之前的版本曾推荐一种编写混入的方式:分别创建运行时层级和类型层级,最后再将它们合并:

ts
// Each mixin is a traditional ES class
class Jumpable {
jump() {}
}
 
class Duckable {
duck() {}
}
 
// Including the base
class Sprite {
x = 0;
y = 0;
}
 
// Then you create an interface which merges
// the expected mixins with the same name as your base
interface Sprite extends Jumpable, Duckable {}
// Apply the mixins into the base class via
// the JS at runtime
applyMixins(Sprite, [Jumpable, Duckable]);
 
let player = new Sprite();
player.jump();
console.log(player.x, player.y);
 
// This can live anywhere in your codebase:
function applyMixins(derivedCtor: any, constructors: any[]) {
constructors.forEach((baseCtor) => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
Object.defineProperty(
derivedCtor.prototype,
name,
Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
Object.create(null)
);
});
});
}
Try

这种模式较少依赖编译器,更多依赖你的代码库来确保运行时和类型系统保持正确同步。

约束

混入模式通过代码流分析在 TypeScript 编译器内部得到原生支持。但在少数情况下,你会触及原生支持的边界。

装饰器与混入 #4881

你无法使用装饰器通过代码流分析来提供混入:

ts
// A decorator function which replicates the mixin pattern:
const Pausable = (target: typeof Player) => {
return class Pausable extends target {
shouldFreeze = false;
};
};
 
@Pausable
class Player {
x = 0;
y = 0;
}
 
// The Player class does not have the decorator's type merged:
const player = new Player();
player.shouldFreeze;
Property 'shouldFreeze' does not exist on type 'Player'.2339Property 'shouldFreeze' does not exist on type 'Player'.
 
// The runtime aspect could be manually replicated via
// type composition or interface merging.
type FreezablePlayer = Player & { shouldFreeze: boolean };
 
const playerTwo = (new Player() as unknown) as FreezablePlayer;
playerTwo.shouldFreeze;
Try

静态属性混入 #17829

与其说是约束,不如说是陷阱。类表达式模式创建的是单例,因此无法在类型系统中映射它们以支持不同的变量类型。

你可以通过使用函数返回基于泛型而有所不同的类来绕过这个问题:

ts
function base<T>() {
class Base {
static prop: T;
}
return Base;
}
 
function derived<T>() {
class Derived extends base<T>() {
static anotherProp: T;
}
return Derived;
}
 
class Spec extends derived<string>() {}
 
Spec.prop; // string
Spec.anotherProp; // string
Try

TypeScript 文档是一个开源项目。欢迎提交 Pull Request,帮助我们改进这些页面 ❤

最后更新:2026 年 10 月 23 日