装饰器是一个附加到类或类的某个部分(例如方法或属性)的函数,用于修改或添加行为。
您可以通过在类或类成员的上方放置 `@` 符号,后跟装饰器函数名来装饰它。
这是一个快速的装饰器示例。您可以在本教程的其余部分了解更多信息。
示例
// Decorator function that logs when a class is created
function logger(value: Function, context: ClassDecoratorContext) {
// Print the name of the class being decorated
console.log(`Creating class - ${context.name}`)
}
// Here, @logger is a decorator that calls the logger() function
@logger
class Person {
constructor(public name: string) {}
}
// Output: Creating class - Person
在此,`logger()` 是一个修改整个类的装饰器函数。在类声明前使用 `@logger` 可确保装饰器在类定义时运行,而不是在实例化时运行。
启用装饰器
要使用装饰器,您需要在 TypeScript 配置文件(即 `tsconfig.json`)中添加以下设置:
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"useDefineForClassFields": true
}
}
装饰器类型
TypeScript 中可用的主要装饰器类型是:
装饰器类型 | 应用于 | 用例 |
---|---|---|
类 | 整个类 | 日志记录、密封 |
属性 | 类字段 | 跟踪访问、验证 |
方法 | 类方法 | 计时、日志记录调用 |
访问器 | Getter/setter | 隐藏、修改行为 |
提示:如果您对装饰器仍然感到困惑,只需将其视为一个“贴纸”,您将其“贴”在类或其成员上以提供额外信息或功能。
1. 类装饰器
我们使用类装饰器来修改类。
- 作用:应用于整个类。
- 接收:类的构造函数。
- 用例:可以修改或密封类。
语法
function decoratorName(value: Function, context: ClassDecoratorContext) {
// Decorator code
}
类装饰器的参数含义如下:
参数 | 类型 | 描述 |
---|---|---|
value (值) |
函数 |
指代正在被装饰的类构造函数。 |
context |
ClassDecoratorContext |
提供有关被装饰类的元数据(例如类的 `name`、其 `kind` 以及 `addInitializer`)。 |
理解装饰器
让我们通过前面的例子来尝试理解装饰器的基本工作原理。
// Create a decorator function
// that logs when a class is created
function logger(value: Function, context: ClassDecoratorContext) {
// Print the name of the class being decorated
console.log(`Decorated Entity: ${context.name}`)
// Print the kind of the decorated entity
console.log(`Entity Kind: ${context.kind}`)
// Print the raw value of the constructor function
console.log("Entity Constructor:")
console.log(value)
}
// Use the decorator on a class
@logger
class Person {
constructor(public name: string) {}
}
输出
Decorated Entity: Person Entity Kind: class Entity Constructor: [class Person]
在这里,装饰器函数 `logger()` 是一个修改类的类装饰器。
在装饰器内部:
- `context.name` - 提供我们将要装饰的实体(类)的名称。
- `context.kind` - 提供我们将要装饰的实体的类型。
- `console.log(value)` - 打印构造函数的原始值。
最后,我们通过在类声明前使用 `@decoratorName` 语法来装饰一个类。
// Use the decorator on a class
@logger
class Person {
constructor(public name: string) {}
}
请注意,我们没有创建类的实例。这表明装饰器在类定义时执行,而不是在实例化时执行。
提示:您可以使用泛型来提高类型安全性。例如:
function logger<T extends Function>(value: T, context: ClassDecoratorContext) {
// Decorator code
}
示例 1:密封类的装饰器
现在,让我们创建一个装饰器来密封一个类,即防止修改其结构,例如添加或删除属性。
function sealed<T extends Function>(value: T, context: ClassDecoratorContext) {
Object.seal(value);
Object.seal(value.prototype);
console.log("Class sealed!");
}
@sealed
class Vehicle {
wheels: number = 4;
}
// Try to add a new property
// Will fail silently or throw error in strict mode
(Vehicle as any).newProp = "test";
console.log((Vehicle as any).newProp);
输出
Class sealed! undefined
2. 属性装饰器
属性装饰器用于修改或扩展类字段的行为。
- 作用:应用于类字段。
- 接收:通过 `ClassFieldDecoratorContext` 提供的元数据。
- 用例:可以记录读写或强制执行规则。
语法
function decoratorName(value: undefined, context: ClassFieldDecoratorContext) {
// Decorator code
}
目前可以忽略 `value` 参数 — 它始终是 `undefined`,因为装饰器在类定义时应用,在创建任何实例之前。
示例 2:TypeScript 属性装饰器
function logAccess(value: undefined, context: ClassFieldDecoratorContext) {
let backingField = Symbol();
context.addInitializer(function () {
Object.defineProperty(this, context.name, {
get() {
console.log(`Getting property: ${String(context.name)}`);
return this[backingField];
},
set(newValue: any) {
console.log(`Setting property: ${String(context.name)} to ${newValue}`);
this[backingField] = newValue;
},
enumerable: true,
configurable: true
});
});
}
class Product {
@logAccess
name: string;
constructor(name: string) {
this.name = name;
}
}
const laptop = new Product("Laptop");
console.log(laptop.name); // Logs access
laptop.name = "Gaming Laptop"; // Logs change
输出
Setting property: name to Laptop Getting property: name Laptop Setting property: name to Gaming Laptop
在这里,`logAccess()` 是一个属性装饰器,它记录对类属性的访问并设置该属性的值。
在此装饰器内部:
- `backingField` 是一个私有的内部键,用于存储被装饰属性(`name`)的实际值,而不会干扰公共接口。
- `context.addInitializer()` 允许您在实例初始化期间使用自定义的 getter 和 setter 来设置属性。
3. 方法装饰器
我们使用方法装饰器来装饰类的方法。
- 作用:应用于方法。
- 接收:通过 `ClassMethodDecoratorContext` 提供的元数据。
- 用例:在方法运行之前/之后添加逻辑。
语法
function decoratorName(target: any, context: ClassMethodDecoratorContext) {
// Decorator code
}
在这里,`target` 是被装饰的原始方法函数。
示例 3:TypeScript 方法装饰器
function measureTime(target: any, context: ClassMethodDecoratorContext) {
const originalMethod = target;
return function (this: any, ...args: any[]) {
console.time("Execution");
const result = originalMethod.apply(this, args);
console.timeEnd("Execution");
return result;
};
}
class Calculator {
@measureTime
add(a: number, b: number): number {
// Simulate delay
for (let i = 0; i < 1000000; i++) {}
return a + b;
}
}
const calc = new Calculator();
calc.add(5, 3);
输出
Execution: 1.648ms
此程序使用一个名为 `measureTime()` 的装饰器来测量 `add()` 方法的运行时间。
该装饰器包装了 `add()` 方法,并使用 `console.time()` 和 `console.timeEnd()` 记录执行时间。
工作原理
当调用 `calc.add(5, 3)` 时,装饰器会:
- 启动一个计时器。
- 运行原始的 `add()` 方法。
- 停止计时器。
- 记录所需时间。
`add()` 中的 `for` 循环会增加一个短暂的延迟,以便我们能够看到计时。
4. 访问器装饰器
访问器装饰器用于修改类中getter 和 setter 的行为。
- 作用:应用于 `get` 或 `set` 访问器。
- 接收:`ClassGetterDecoratorContext` 或 `ClassSetterDecoratorContext`。
- 用例:控制访问器的可见性或行为。
语法
function decoratorName(
value: any,
context: ClassGetterDecoratorContext | ClassSetterDecoratorContext
) {
// Decorator code
}
在这里,`value` 是实际的 getter 或 setter 函数。由于我们为 `context` 使用了联合类型,该装饰器可以同时用于 getter 和 setter。
示例 4:TypeScript 访问器装饰器
function isHidden(
value: any,
context: ClassGetterDecoratorContext | ClassSetterDecoratorContext
) {
context.addInitializer(function () {
Object.defineProperty(this, context.name, {
enumerable: false
});
});
return value;
}
class UserProfile {
private _age: number;
constructor(age: number) {
this._age = age;
}
@isHidden
get age(): number {
return this._age;
}
set age(value: number) {
this._age = value;
}
}
const user = new UserProfile(30);
// Using the getter gives 'undefined' because it is hidden
console.log(user.age);
// Verifying that '_age' property still exists
console.log(Object.keys(user));
输出
undefined [ '_age' ]
在这里,`isHidden()` 装饰器将访问器从对象枚举中隐藏。在本例中,我们隐藏了 `get age()` 的枚举。
因此,您将无法查看 `_age` 属性,因为其 getter 函数已被隐藏。