Mixin

除了传统的 OO 层次结构之外,另一种流行的从可重用组件构建类的做法是通过组合更简单的部分类来构建它们。您可能熟悉 Scala 等语言的 mixin 或 trait 的概念,这种模式在 JavaScript 社区也越来越流行。

Mixin 如何工作?

这种模式依赖于使用泛型和类继承来扩展基类。TypeScript 最佳的 mixin 支持是通过类表达式模式实现的。您可以阅读更多关于这种模式在 JavaScript 中如何工作的信息 这里

首先,我们需要一个类,在其之上应用 mixin

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

设置好这些后,就可以创建一个类来表示应用了 mixin 的基类。

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

受限 Mixin

在上述形式中,mixin 对类没有底层知识,这可能难以创建你想要的设计。

为了模拟这种情况,我们修改了原始构造函数类型以接受泛型参数。

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

然后,你可以创建仅在你有特定基类可供构建时才起作用的 mixin。

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

替代模式

本文档的早期版本建议了一种编写 mixin 的方法,你可以在其中分别创建运行时和类型层次结构,然后在最后将它们合并。

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 编译器通过代码流分析原生支持 mixin 模式。在一些情况下,你可能会遇到原生支持的边缘情况。

装饰器和 Mixin #4881

你不能使用装饰器通过代码流分析来提供 mixin。

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

静态属性 Mixin #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

本页贡献者
OTOrta Therox (16)
GMGleb Maksimenko (1)
IOIván Ovejero (1)
DEDom Eccleston (1)
OOblosys (1)
5+

上次更新:2024 年 3 月 21 日