从我记事起,装饰器几乎就已经是 ECMAScript 的一部分了。这些漂亮的工具让我们能够以可重用的方式修改类和成员。他们已经在 TypeScript 中出现了一段时间------尽管是在实验旗帜下。尽管装饰器的第 2 阶段迭代始终处于实验阶段,但装饰器已广泛应用于MobX、Angular、Nest和TypeORM等库中。TypeScript 5.0 的装饰器与ECMAScript 提案完全同步,ECMAScript 提案已经为黄金时段做好了准备,处于第 3 阶段。
装饰器让我们可以设计一个函数来调整类及其方法的行为。想象一下需要将一些调试语句潜入我们的方法中。在 TypeScript 5.0 之前,我们必须在每个方法中手动复制和粘贴调试语句。使用装饰器,我们只需完成一次工作,并且将通过装饰器附加的每个方法来支持更改。
假设我们想要创建一个装饰器来记录给定方法已被弃用:
class Card {
constructor(public suit: Suit, public rank: Rank) {
this.suit = suit;
this.rank = rank;
}
get name(): CardName {
return `${this.rank} of ${this.suit}`;
}
@deprecated // This is a decorator!
getValue(): number {
if (this.rank === 'Ace') return 14;
if (this.rank === 'King') return 13;
if (this.rank === 'Queen') return 12;
if (this.rank === 'Jack') return 11;
return this.rank;
}
// The new way to do it!
get value(): number {
if (this.rank === 'Ace') return 14;
if (this.rank === 'King') return 13;
if (this.rank === 'Queen') return 12;
if (this.rank === 'Jack') return 11;
return this.rank;
}
}
const card = new Card('Spades', 'Queen');
card.getValue();
card.getValue()
我们希望每当调用时都会将警告消息记录到控制台。我们可以按如下方式实现上面的装饰器:
const deprecated = <This, Arguments extends any[], ReturnValue>(
target: (this: This, ...args: Arguments) => ReturnValue,
context: ClassMethodDecoratorContext<
This,
(this: This, ...args: Arguments) => ReturnValue
>,
) => {
const methodName = String(context.name);
function replacementMethod(this: This, ...args: Arguments): ReturnValue {
console.warn(`Warning: '${methodName}' is deprecated.`);
return target.call(this, ...args);
}
return replacementMethod;
};
乍一看这可能有点令人困惑,但让我们来分解一下:
-
我们的装饰器函数有两个参数:
target
和context
。 -
target
是我们正在装饰的方法本身。 -
context
是有关该方法的元数据。 -
我们返回一些具有相同签名的方法。
-
在本例中,我们调用
console.warn
以记录弃用通知,然后调用该方法。
该ClassMethodDecorator
类型具有以下属性:
-
kind
:装饰属性的类型。在上面的示例中,这将是method
,因为我们正在 a 实例上修饰一个方法Card
。 -
name
: 财产名称。在上面的例子中,这是getValue
. -
static
:指示类元素是静态 (true
) 还是实例 (false
) 元素的值。 -
private
:指示类元素是否具有私有名称的值。 -
access
:可用于在运行时访问类元素的当前值的对象。 -
has
:确定对象是否具有与装饰元素同名的属性。 -
get
:调用所提供对象上的 setter。
装饰器提供了方便的语法糖来添加日志消息(就像我们在上面的示例中所做的那样)以及许多其他常见用例。例如,我们可以创建一个装饰器,自动将方法绑定到当前实例,或者修改方法或类的属性描述符。