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)

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

相关推荐
沙蒿同学18 小时前
不引 Ghostscript:我用纯 Go 给 PDF 做了一场「压缩手术」
前端·后端·github
Linux_cy18 小时前
服务器管理工具哪个好用?我用了七八个
运维·服务器·javascript·python·ssh·面板·gmssh
变与不变80619 小时前
jQuery细节以及使用应用场景
前端·javascript·jquery
SailingCoder20 小时前
【封闭结束1】端到端交付:Agent 一次请求如何走完全链路
前端·安全·ai·面试·agent·harness
风骏时光牛马1 天前
AI多模块协同自动化工作流
前端
zhangminghuan1 天前
前端面试应对技巧
前端·面试·职场和发展
康小旺1 天前
TypeScript 编译器开始卷 Rust 了:Cloudflare 的 tsrs 到底想干嘛?
rust·typescript
计算机魔术师1 天前
潜伏14年的PHP致命洞,AI跑两天就交卷了
前端
IT_陈寒1 天前
Vue的这个响应式陷阱我居然现在才发现
前端·人工智能·后端