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 不允许同时装饰同一个成员的 getset 访问器,只能选择其中一个装饰,因为两者的属性描述符是合并在一起的。

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)

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

相关推荐
一拳不是超人2 小时前
一个没测暗色模式的 Bug,吃掉了我一半 谷歌扩展用户
前端·javascript·程序员
liangshanbo12152 小时前
React useState 函数式更新面试题整理
前端·react.js·前端框架
涛涛ing2 小时前
为什么你的页面在 Safari 上总出问题?Interop 2027 正在解决这个 20 年老毛病
前端
不一样的少年_3 小时前
WebP 压缩到底在干嘛?小白也能看懂的原理拆解
前端·后端·图片资源
问心无愧05133 小时前
ctf show web 177
前端·笔记
不一样的少年_3 小时前
PNG/JPG 如何变成 WebP?真相不是改后缀!
前端·后端·图片资源
杉氧3 小时前
RN 性能调优指南:重渲染(Re-renders)控制与长列表(FlatList)优化
android·前端·react native
一_个前端3 小时前
[JS] 一站式搞定 PDF、图片、Dom弹窗、表格的浏览器打印功能
前端
JavaGuide3 小时前
阿里 Qoder 又开源了一个专门给 Claude Code、Codex 做“体检”的项目
前端·后端