当前位置: 首页 > news >正文

TypeScript 装饰器详解

装饰器(Decorator)是 TypeScript 中的一个重要特性,它提供了一种声明式的方式来修改类、方法、属性或参数的行为。装饰器使用 @expression 语法,其中 expression 是一个函数,它会在运行时被调用,并接收被装饰目标的元数据。

1. 装饰器基础

1.1. 启用装饰器

在 tsconfig.json 中启用实验性装饰器支持:

{"compilerOptions": {"experimentalDecorators": true,"emitDecoratorMetadata": true}
}

2. 装饰器类型

2.1. 类装饰器

类装饰器应用于类构造函数,可以用来观察、修改或替换类定义。

function sealed(constructor: Function) {Object.seal(constructor);Object.seal(constructor.prototype);
}@sealed
class Greeter {greeting: string;constructor(message: string) {this.greeting = message;}greet() {return "Hello, " + this.greeting;}
}

通过定义类装饰器工厂还可以传递参数给装饰器:

function color(value: string) {return function (constructor: Function) {constructor.prototype.color = value;};
}@color("red")
class Car {// ...
}const myCar = new Car();
console.log((myCar as any).color); // 输出: red

2.2. 方法装饰器

方法装饰器应用于方法的属性描述符,可以用来观察、修改或替换方法定义。

function enumerable(value: boolean) {return function (target: any,propertyKey: string,descriptor: PropertyDescriptor) {descriptor.enumerable = value;};
}class Greeter {greeting: string;constructor(message: string) {this.greeting = message;}@enumerable(false)greet() {return "Hello, " + this.greeting;}
}

2.3. 访问器装饰器

访问器装饰器应用于访问器的属性描述符,可以用来观察、修改或替换访问器的定义。

function configurable(value: boolean) {return function (target: any,propertyKey: string,descriptor: PropertyDescriptor) {descriptor.configurable = value;};
}class Point {private _x: number;private _y: number;constructor(x: number, y: number) {this._x = x;this._y = y;}@configurable(false)get x() {return this._x;}@configurable(false)get y() {return this._y;}
}

2.4. 属性装饰器

属性装饰器应用于类的属性。

function format(formatString: string) {return function (target: any, propertyKey: string) {let value = target[propertyKey];const getter = function () {return `${formatString} ${value}`;};const setter = function (newVal: string) {value = newVal;};Object.defineProperty(target, propertyKey, {get: getter,set: setter,enumerable: true,configurable: true,});};
}class Greeter {@format("Hello")greeting: string;constructor(message: string) {this.greeting = message;}
}const greeter = new Greeter("World");
console.log(greeter.greeting); // 输出: Hello World

2.5. 参数装饰器

参数装饰器应用于构造函数或方法的一个参数。

function validate(target: any,propertyKey: string,parameterIndex: number
) {const validParams: number[] = Reflect.getOwnMetadata("validParams", target, propertyKey) || [];validParams.push(parameterIndex);Reflect.defineMetadata("validParams", validParams, target, propertyKey);
}class Greeter {greet(@validate name: string) {return "Hello " + name;}
}

3. 装饰器执行顺序

装饰器的应用顺序如下:

1. 参数装饰器,然后依次是方法装饰器、访问器装饰器或属性装饰器应用到每个实例成员;

2. 参数装饰器,然后依次是方法装饰器、访问器装饰器或属性装饰器应用到每个静态成员;

3. 参数装饰器应用到构造函数;

4. 类装饰器应用到类;

4. 实际应用示例

4.1. 日志装饰器

function log(target: any,propertyKey: string,descriptor: PropertyDescriptor
) {const originalMethod = descriptor.value;descriptor.value = function (...args: any[]) {console.log(`Calling ${propertyKey} with args: ${JSON.stringify(args)}`);const result = originalMethod.apply(this, args);console.log(`Called ${propertyKey}, returned: ${JSON.stringify(result)}`);return result;};return descriptor;
}class Calculator {@logadd(a: number, b: number) {return a + b;}
}const calculator = new Calculator();
calculator.add(2, 3);
// 输出:
// Calling add with args: [2,3]
// Called add, returned: 5

4.2. 验证装饰器

function validateEmail(target: any,propertyKey: string,parameterIndex: number
) {const validateParams: number[] = Reflect.getOwnMetadata("validateParams", target, propertyKey) || [];validateParams.push(parameterIndex);Reflect.defineMetadata("validateParams", validateParams, target, propertyKey);
}function validate(target: any,propertyKey: string,descriptor: PropertyDescriptor
) {const originalMethod = descriptor.value;const validateParams: number[] = Reflect.getOwnMetadata("validateParams", target, propertyKey) || [];descriptor.value = function (...args: any[]) {for (const index of validateParams) {if (!/^\S+@\S+\.\S+$/.test(args[index])) {throw new Error(`Invalid email: ${args[index]}`);}}return originalMethod.apply(this, args);};return descriptor;
}class UserService {@validatecreateUser(name: string,@validateEmail email: string,age: number) {return { name, email, age };}
}const userService = new UserService();
userService.createUser("Alice", "alice@example.com", 25); // 正常
userService.createUser("Bob", "invalid-email", 30); // 抛出错误

4.3. 依赖注入装饰器

const serviceRegistry = new Map<string, any>();function Injectable(constructor: Function) {serviceRegistry.set(constructor.name, new constructor());
}function Inject(serviceName: string) {return function (target: any, propertyKey: string) {Object.defineProperty(target, propertyKey, {get: () => serviceRegistry.get(serviceName),enumerable: true,configurable: true,});};
}@Injectable
class LoggerService {log(message: string) {console.log(`[LOG]: ${message}`);}
}class AppComponent {@Inject("LoggerService")logger!: LoggerService;run() {this.logger.log("Application started");}
}const app = new AppComponent();
app.run(); // 输出: [LOG]: Application started

5. 元数据反射 API

TypeScript 与 reflect-metadata 库结合使用可以提供更强大的装饰器功能:

npm install reflect-metadata

然后在入口文件顶部添加:

import "reflect-metadata";

使用示例:

function logType(target: any, key: string) {const type = Reflect.getMetadata("design:type", target, key);console.log(`${key} type: ${type.name}`);
}class Demo {@logTypepublic attr1: string = "hello";@logTypepublic attr2: number = 42;
}// 输出:
// attr1 type: String
// attr2 type: Number

6. 装饰器与 Angular

Angular 框架大量使用装饰器:

import { Component, Input, Output, EventEmitter } from '@angular/core';@Component({selector: 'app-greeter',template: `<h1>Hello {{name}}!</h1><button (click)="onClick()">Click me</button>`
})
export class GreeterComponent {@Input() name: string = 'World';@Output() clicked = new EventEmitter<void>();onClick() {this.clicked.emit();}
}

7. 总结

TypeScript 装饰器提供了强大的元编程能力,可以用于:

1. 添加元数据;

2. 修改或扩展类、方法、属性的行为;

3. 实现 AOP (面向切面编程);

4. 依赖注入;

5. 验证和转换数据;

6. 日志记录和性能监控;

装饰器在 Angular、NestJS 等框架中广泛应用,是 TypeScript 高级开发的重要工具。

相关文章:

  • Kotlin Multiplatform--03:项目实战
  • 六大设计原则
  • 2025低空经济发展趋势
  • css背景相关
  • PyGame游戏开发(含源码+演示视频+开结题报告+设计文档)
  • spark算子介绍
  • 单片机-STM32部分:12、I2C
  • Redis设计与实现——数据结构与对象
  • python实战项目69:基于Python爬虫的链家二手房数据采集方法研究
  • 宝塔centos7.6安装redis失败
  • DeepSeek:开启能源领域智能化变革新时代
  • Linux `uname` 指令终极指南
  • SpEL(Spring Expression Language)使用详解
  • CSS Layer 详解
  • Linux : 多线程【线程概念】
  • 复现MAET的环境问题(自用)
  • linux基础操作4------(权限管理)
  • HTTP 和 WebSocket 的区别
  • AAAI-2025 | 视觉定位的深度语义对齐!SSRVG:基于内容与结构信息的视觉定位
  • Day01 ST表——倍增表
  • 美国4月CPI同比上涨2.3%低于预期,为2021年2月来最小涨幅
  • 乌方:泽连斯基只接受与普京会谈,拒见其他俄代表
  • 最高降九成!特朗普签署降药价行政令落地存疑,多家跨国药企股价收涨
  • 教育部:启动实施县中头雁教师岗位计划,支撑县中全面振兴
  • 行知读书会|换一个角度看见社会
  • 新造古镇丨乌镇的水太包容了,可以托举住任何一种艺术