文章目录

TypeScript 装饰器:类、方法、属性装饰器

发布于 2026-04-03 09:59:26 · 浏览 8 次 · 评论 0 条

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 类及其原型对象被密封,无法添加或删除属性。


方法装饰器

方法装饰器作用于类中的某个方法。它接收三个参数:

  1. 目标对象(对于静态方法是类构造函数,对于实例方法是原型对象)
  2. 方法名
  3. 属性描述符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) 会输出方法调用和返回值的日志。


属性装饰器

属性装饰器作用于类的属性声明。它接收两个参数:

  1. 目标对象(静态属性是类构造函数,实例属性是原型对象)
  2. 属性名

注意:由于 TypeScript 编译限制,属性装饰器无法访问属性的初始值,也不能直接修改属性值。通常用于收集元数据。

定义 一个标记必填字段的装饰器:

const requiredFields: string[] = [];

function required(target: any, propertyName: string) {
  requiredFields.push(propertyName);
}

使用 它标记属性:

class Form {
  @required
  email: string = '';

  @required
  password: string = '';
}

后续可通过 requiredFields 数组获取所有必填字段名(实际项目中应将元数据存储在更安全的位置,如使用 Reflect API)。


装饰器执行顺序

当多个装饰器同时存在时,执行顺序有明确规则:

  1. 属性装饰器 先于 方法装饰器 执行。
  2. 对于同一类型的多个装饰器(如两个属性装饰器),从下到上 执行(即离目标最近的先执行)。
  3. 类装饰器 最后执行。

例如:

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;
  };
}

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文