TypeScript 装饰器:类、方法、属性装饰器
TypeScript 装饰器是一种特殊语法,用于在类、方法、属性或参数上添加元数据或修改行为。它们本质上是函数,在编译时被调用,常用于日志记录、权限控制、自动绑定等场景。要使用装饰器,必须在 tsconfig.json 中启用实验性装饰器支持。
启用装饰器支持
打开 项目根目录下的 tsconfig.json 文件。
添加或修改 以下编译选项:
{
"compilerOptions": {
"target": "ES2022",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
"experimentalDecorators": true允许使用装饰器语法。"emitDecoratorMetadata": true会为装饰的目标生成类型元数据(需配合reflect-metadata库使用)。
装饰器的基本形式
装饰器是一个函数,根据应用位置不同,接收的参数也不同。以下是三种常见装饰器的定义方式。
类装饰器
类装饰器作用于整个类构造函数。它接收一个参数:目标类的构造函数。
定义 一个简单的类装饰器:
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
使用 它修饰一个类:
@sealed
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
效果:User 类及其原型对象被密封,无法添加或删除属性。
方法装饰器
方法装饰器作用于类中的某个方法。它接收三个参数:
- 目标对象(对于静态方法是类构造函数,对于实例方法是原型对象)
- 方法名
- 属性描述符(
PropertyDescriptor)
定义 一个日志装饰器:
function log(target: any, propertyName: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`调用方法 ${propertyName},参数:`, args);
const result = originalMethod.apply(this, args);
console.log(`方法 ${propertyName} 返回:`, result);
return result;
};
}
使用 它修饰实例方法:
class Calculator {
@log
add(a: number, b: number): number {
return a + b;
}
@log
static multiply(x: number, y: number): number {
return x * y;
}
}
调用 new Calculator().add(2, 3) 会输出方法调用和返回值的日志。
属性装饰器
属性装饰器作用于类的属性声明。它接收两个参数:
- 目标对象(静态属性是类构造函数,实例属性是原型对象)
- 属性名
注意:由于 TypeScript 编译限制,属性装饰器无法访问属性的初始值,也不能直接修改属性值。通常用于收集元数据。
定义 一个标记必填字段的装饰器:
const requiredFields: string[] = [];
function required(target: any, propertyName: string) {
requiredFields.push(propertyName);
}
使用 它标记属性:
class Form {
@required
email: string = '';
@required
password: string = '';
}
后续可通过 requiredFields 数组获取所有必填字段名(实际项目中应将元数据存储在更安全的位置,如使用 Reflect API)。
装饰器执行顺序
当多个装饰器同时存在时,执行顺序有明确规则:
- 属性装饰器 先于 方法装饰器 执行。
- 对于同一类型的多个装饰器(如两个属性装饰器),从下到上 执行(即离目标最近的先执行)。
- 类装饰器 最后执行。
例如:
function A() {
console.log('A');
return function () {};
}
function B() {
console.log('B');
return function () {};
}
@A()
class Example {
@B()
@A()
method() {}
}
输出顺序为:
A // 属性/方法装饰器 A(靠近 method)
B // 属性/方法装饰器 B
A // 类装饰器 A
实战:自动绑定 this 的装饰器
在 React 或事件处理中,常需手动绑定方法的 this。可用装饰器自动完成。
编写 autobind 装饰器:
function autobind(
target: any,
propertyName: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
const adjustedDescriptor: PropertyDescriptor = {
configurable: true,
get() {
const boundFn = originalMethod.bind(this);
return boundFn;
}
};
return adjustedDescriptor;
}
使用 它避免手动绑定:
class ButtonHandler {
message = '点击成功!';
@autobind
handleClick() {
alert(this.message); // this 指向 ButtonHandler 实例
}
}
const handler = new ButtonHandler();
document.getElementById('btn')!.onclick = handler.handleClick;
点击按钮时,this.message 能正确访问,无需在构造函数中写 this.handleClick = this.handleClick.bind(this)。
注意事项与最佳实践
-
装饰器是实验性特性,虽然广泛使用,但标准仍在演进。生产项目建议锁定 TypeScript 版本。
-
避免在装饰器中执行副作用操作(如网络请求),因其在模块加载时运行,而非实例创建时。
-
使用
emitDecoratorMetadata时,需在入口文件导入并初始化reflect-metadata:import 'reflect-metadata'; -
装饰器不能用于函数声明(因函数提升问题),仅适用于类及其成员。
常见装饰器应用场景
| 场景 | 装饰器类型 | 说明 |
|---|---|---|
| 权限校验 | 方法装饰器 | 在方法执行前检查用户角色 |
| 缓存结果 | 方法装饰器 | 对相同参数的调用返回缓存值 |
| 验证输入 | 参数装饰器 | 标记参数是否必需或符合格式(需配合方法装饰器) |
| 序列化配置 | 属性装饰器 | 标记哪些属性需要参与 JSON 序列化 |
| 单例模式 | 类装饰器 | 确保类只有一个实例 |
// 示例:缓存装饰器
function memoize(target: any, propertyName: string, descriptor: PropertyDescriptor) {
const method = descriptor.value;
const cacheKey = `${propertyName}_cache`;
descriptor.value = function (...args: any[]) {
if (!this[cacheKey]) this[cacheKey] = new Map();
const key = JSON.stringify(args);
if (this[cacheKey].has(key)) {
return this[cacheKey].get(key);
}
const result = method.apply(this, args);
this[cacheKey].set(key, result);
return result;
};
}
暂无评论,快来抢沙发吧!