Typescript装饰器

在 TypeScript 中使用装饰器之前,需要确保在 tsconfig.json 中开启了 experimentalDecorators:

json 复制代码
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true // 如果需要反射元数据
  }
}

一、简介 (Introduction)

什么是装饰器?

装饰器是一种特殊类型的声明,它能够被附加到类声明、方法、访问器(get/set)、属性或参数上。本质上,装饰器就是一个在运行时被调用的函数。

核心特点:

  • 使用 @expression 语法。
  • 用于修改类或其成员的行为,不改变原有代码结构。
  • 目前 TypeScript 装饰器主要遵循早期的 Stage 2 提案(在 Angular、NestJS 等框架中被广泛使用)。

二、类装饰器 (Class Decorators)

定义: 声明在类声明之前,应用于类构造函数。

签名: (constructor: Function) => void | Function

执行时机: 类定义时(而不是实例化时)执行。

typescript 复制代码
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
  console.log("类装饰器执行了");
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
}
// 输出: 类装饰器执行了

如果类装饰器返回一个新的构造函数,它会替换原来的类定义。


三、装饰器工厂 (Decorator Factories)

定义: 如果想给装饰器传递参数,就必须使用装饰器工厂。它本质上是一个返回装饰器函数的函数。

签名: (...args: any[]) => (target: any, ...) => void

typescript 复制代码
function color(value: string) { // 这就是装饰器工厂
  return function (constructor: Function) { // 这是真正的装饰器
    console.log(`给类 ${constructor.name} 应用了颜色: ${value}`);
  };
}

@color("红色")
class Car {}
// 输出: 给类 Car 应用了颜色: 红色

四、装饰器组合 (Decorator Composition)

定义: 在同一个目标上可以应用多个装饰器。

执行顺序(非常重要):

  • 表达式从上往下求值 (即先执行 @f(),再执行 @g())。
  • 结果从下往上调用 (即先执行 @g 返回的函数,再执行 @f 返回的函数)。
typescript 复制代码
function f() {
  console.log("f(): evaluated");
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("f(): called");
  };
}

function g() {
  console.log("g(): evaluated");
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    console.log("g(): called");
  };
}

class C {
  @f()
  @g()
  method() {}
}
// 输出顺序:
// f(): evaluated
// g(): evaluated
// g(): called
// f(): called

五、属性装饰器 (Property Decorators)

定义: 声明在属性声明之前。

签名: (target: Object, propertyKey: string | symbol) => void

注意: 属性装饰器没有 descriptor(属性描述符),因为属性通常在构造函数中初始化,而不是在原型上。

typescript 复制代码
function format(target: any, propertyKey: string) {
  console.log(`属性装饰器作用于: ${propertyKey}`);
}

class User {
  @format
  name: string = "张三";
}

应用场景:配合 reflect-metadata 做数据校验、序列化等。


六、方法装饰器 (Method Decorators)

定义: 声明在方法声明之前。

签名: (target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) => PropertyDescriptor | void

用途: 修改、替换或包装方法。这是最常用的装饰器之一(如日志记录、性能监控、修改返回值)。

typescript 复制代码
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  const originalMethod = descriptor.value;
  descriptor.value = function (...args: any[]) {
    console.log(`调用方法: ${propertyKey},参数: ${JSON.stringify(args)}`);
    return originalMethod.apply(this, args);
  };
}

class Calculator {
  @log
  add(a: number, b: number) {
    return a + b;
  }
}

new Calculator().add(1, 2);
// 输出: 调用方法: add,参数: [1,2]

七、访问器装饰器 (Accessor Decorators)

定义: 声明在 getter 或 setter 之前。

签名: 与方法装饰器相同 (target, propertyKey, descriptor)。

注意: TypeScript 不允许同时装饰同一个成员的 get 和 set 访问器,只能选择其中一个装饰,因为两者的属性描述符是合并在一起的。

typescript 复制代码
function configurable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.configurable = value;
  };
}

class Point {
  private _x: number = 0;

  @configurable(false)
  get x() { return this._x; }
}

八、参数装饰器 (Parameter Decorators)

定义: 声明在方法参数或构造函数参数之前。

签名: (target: Object, propertyKey: string | symbol, parameterIndex: number) => void

执行时机: 参数装饰器会在类装饰器、方法装饰器之前 被调用。

用途: 提取参数元数据,常用于依赖注入(DI)框架(如 Angular、NestJS)。

typescript 复制代码
function inject(target: any, propertyKey: string, parameterIndex: number) {
  console.log(`参数装饰器: 方法 ${propertyKey} 的第 ${parameterIndex} 个参数需要注入`);
}

class Service {
  constructor(@inject private db: any) {}
  
  query(@inject data: string) {
    // ...
  }
}
// 输出: 
// 参数装饰器: 方法 query 的第 0 个参数需要注入
// 参数装饰器: 方法 undefined 的第 0 个参数需要注入 (构造函数中 propertyKey 为 undefined)

💡 总结执行顺序

对于同一个类,不同类型的装饰器执行顺序如下:

  1. 参数装饰器 (Parameter)
  2. 方法/访问器/属性装饰器 (Method/Accessor/Property)
  3. 类装饰器 (Class)

而同类装饰器组合时,遵循从上到下求值,从下到上执行的原则。

相关推荐
沙漠之主6 小时前
C++编程教学设计资料:从入门到实战的完整课程方案
java·前端·c++
hasty6 小时前
不上传新包,也能改变用户拿到的版本:npm dist-tag 的 OIDC 权限治理
前端·npm·node.js
Csvn6 小时前
diff 算法(虚拟 DOM Reconciliation)
前端
前端snow7 小时前
ai agent --- 文件存储
前端
Frag0ut7 小时前
Chrome四大版本获取及共存指南:Stable/Beta/Dev/Canary
前端·chrome·浏览器·dev·beta·canary·共存版
IT_陈寒9 小时前
SpringBoot自动配置坑了我一把,原来是这样绕过去的
前端·人工智能·后端
广州华水科技9 小时前
单北斗GNSS变形监测在大坝安全监测中的应用与优势
前端
zeng不错9 小时前
B站 173 个投稿活动看到眼瞎?我写了个扩展,主打一个精准打击
前端
Latchh9 小时前
前端导出的JPEG红字发糊,质量要拉到100才清楚
前端·图像处理·人工智能·计算机视觉
默_笙10 小时前
🚦 请三个 AI 考官给 RAG 打分:量化评估实战(下)
前端·javascript