简体中文 繁體中文 English Deutsch 한국 사람 بالعربية TÜRKÇE português คนไทย Français Japanese

站内搜索

搜索

活动公告

通知:为庆祝网站一周年,将在5.1日与5.2日开放注册,具体信息请见后续详细公告
04-22 00:04
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

TypeScript装饰器模式完全指南掌握这一强大特性让你的代码焕然一新

SunJu_FaceMall

3万

主题

1158

科技点

3万

积分

白金月票

碾压王

积分
32796

立华奏

发表于 2025-10-2 15:10:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
TypeScript装饰器是一种特殊类型的声明,它可以附加到类声明、方法、访问符、属性或参数上。装饰器使用@expression这种形式,其中expression必须求值为一个函数,该函数会在运行时被调用,被装饰的声明信息作为参数传入。

装饰器是TypeScript中的一项实验性特性,它受到了Python中装饰器的启发,并在Angular、NestJS等现代框架中得到了广泛应用。通过装饰器,我们可以在不修改原有代码的情况下,动态地扩展类的功能,实现代码的复用和关注点分离。

装饰器基础

在TypeScript中,装饰器本质上是一个函数,它接收目标对象(被装饰的元素)作为参数,并可以对其进行修改或扩展。装饰器可以在编译时或运行时执行,这取决于装饰器的类型和配置。

要启用装饰器支持,你需要在tsconfig.json文件中设置experimentalDecorators选项为true:
  1. {
  2.   "compilerOptions": {
  3.     "experimentalDecorators": true,
  4.     "emitDecoratorMetadata": true
  5.   }
  6. }
复制代码

装饰器的基本语法如下:
  1. function simpleDecorator(target: any) {
  2.   // 装饰器逻辑
  3.   console.log("装饰器被调用");
  4.   console.log("目标对象:", target);
  5. }
  6. @simpleDecorator
  7. class MyClass {
  8.   // 类定义
  9. }
复制代码

类装饰器

类装饰器应用于类构造函数,可以用来监视、修改或替换类定义。类装饰器在类声明时执行,而不是在类实例化时执行。

类装饰器的函数签名如下:
  1. function classDecorator<T extends { new(...args: any[]): {} }>(constructor: T) {
  2.   return class extends constructor {
  3.     // 新的类定义
  4.   }
  5. }
复制代码

下面是一个简单的类装饰器示例:
  1. function sealed(constructor: Function) {
  2.   Object.seal(constructor);
  3.   Object.seal(constructor.prototype);
  4.   console.log("类已被密封");
  5. }
  6. @sealed
  7. class BugReport {
  8.   type = "report";
  9.   title: string;
  10.   constructor(t: string) {
  11.     this.title = t;
  12.   }
  13. }
  14. // 尝试添加新属性将会失败
  15. // BugReport.prototype.newProperty = "test"; // 在严格模式下会报错
复制代码

更复杂的类装饰器可以修改或扩展类的行为:
  1. function logClass<T extends { new(...args: any[]): {} }>(constructor: T) {
  2.   return class extends constructor {
  3.     // 创建新的构造函数
  4.     constructor(...args: any[]) {
  5.       super(...args);
  6.       console.log(`创建 ${constructor.name} 实例,参数: ${args.join(", ")}`);
  7.     }
  8.   }
  9. }
  10. @logClass
  11. class User {
  12.   name: string;
  13.   age: number;
  14.   constructor(name: string, age: number) {
  15.     this.name = name;
  16.     this.age = age;
  17.   }
  18.   greet() {
  19.     console.log(`Hello, my name is ${this.name} and I'm ${this.age} years old.`);
  20.   }
  21. }
  22. const user = new User("Alice", 30);
  23. // 输出: 创建 User 实例,参数: Alice, 30
  24. user.greet();
  25. // 输出: Hello, my name is Alice and I'm 30 years old.
复制代码

方法装饰器

方法装饰器应用于方法的属性描述符,可以用来监视、修改或替换方法定义。方法装饰器在运行时接收三个参数:

1. 对于静态成员,是类的构造函数;对于实例成员,是类的原型
2. 成员的名称
3. 成员的属性描述符

方法装饰器的函数签名如下:
  1. function methodDecorator(
  2.   target: Object,
  3.   propertyKey: string | symbol,
  4.   descriptor: PropertyDescriptor
  5. ) {
  6.   // 装饰器逻辑
  7. }
复制代码

下面是一个简单的方法装饰器示例:
  1. function log(
  2.   target: Object,
  3.   propertyKey: string | symbol,
  4.   descriptor: PropertyDescriptor
  5. ) {
  6.   const originalMethod = descriptor.value;
  7.   
  8.   descriptor.value = function(...args: any[]) {
  9.     console.log(`调用方法 ${propertyKey.toString()},参数: ${JSON.stringify(args)}`);
  10.     const result = originalMethod.apply(this, args);
  11.     console.log(`方法 ${propertyKey.toString()} 返回: ${JSON.stringify(result)}`);
  12.     return result;
  13.   };
  14.   
  15.   return descriptor;
  16. }
  17. class Calculator {
  18.   @log
  19.   add(a: number, b: number): number {
  20.     return a + b;
  21.   }
  22. }
  23. const calculator = new Calculator();
  24. const result = calculator.add(2, 3);
  25. // 输出:
  26. // 调用方法 add,参数: [2,3]
  27. // 方法 add 返回: 5
复制代码

方法装饰器也可以用于修改方法的访问权限:
  1. function readonly(
  2.   target: Object,
  3.   propertyKey: string | symbol,
  4.   descriptor: PropertyDescriptor
  5. ) {
  6.   descriptor.writable = false;
  7.   return descriptor;
  8. }
  9. class Employee {
  10.   private _salary: number = 1000;
  11.   
  12.   @readonly
  13.   get salary(): number {
  14.     return this._salary;
  15.   }
  16. }
  17. const employee = new Employee();
  18. console.log(employee.salary); // 输出: 1000
  19. // 尝试修改getter将会失败
  20. // employee.salary = 2000; // 在严格模式下会报错
复制代码

属性装饰器

属性装饰器应用于类的属性,可以用来监视或修改属性的定义。属性装饰器在运行时接收两个参数:

1. 对于静态成员,是类的构造函数;对于实例成员,是类的原型
2. 属性的名称

属性装饰器的函数签名如下:
  1. function propertyDecorator(
  2.   target: Object,
  3.   propertyKey: string | symbol
  4. ) {
  5.   // 装饰器逻辑
  6. }
复制代码

下面是一个简单的属性装饰器示例:
  1. function format(formatString: string) {
  2.   return function (target: Object, propertyKey: string | symbol) {
  3.     let value: string;
  4.    
  5.     const getter = function() {
  6.       return `${formatString} ${value} ${formatString}`;
  7.     };
  8.    
  9.     const setter = function(newVal: string) {
  10.       value = newVal;
  11.     };
  12.    
  13.     Object.defineProperty(target, propertyKey, {
  14.       get: getter,
  15.       set: setter,
  16.       enumerable: true,
  17.       configurable: true
  18.     });
  19.   };
  20. }
  21. class Greeter {
  22.   @format("***")
  23.   greeting: string;
  24. }
  25. const greeter = new Greeter();
  26. greeter.greeting = "Hello";
  27. console.log(greeter.greeting); // 输出: *** Hello ***
复制代码

属性装饰器也可以用于验证属性值:
  1. function validateEmail(target: Object, propertyKey: string | symbol) {
  2.   let value: string;
  3.   
  4.   const getter = function() {
  5.     return value;
  6.   };
  7.   
  8.   const setter = function(newVal: string) {
  9.     const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  10.     if (!emailRegex.test(newVal)) {
  11.       throw new Error("Invalid email format");
  12.     }
  13.     value = newVal;
  14.   };
  15.   
  16.   Object.defineProperty(target, propertyKey, {
  17.     get: getter,
  18.     set: setter,
  19.     enumerable: true,
  20.     configurable: true
  21.   });
  22. }
  23. class User {
  24.   @validateEmail
  25.   email: string;
  26.   
  27.   constructor(email: string) {
  28.     this.email = email;
  29.   }
  30. }
  31. const user1 = new User("test@example.com"); // 正确
  32. console.log(user1.email); // 输出: test@example.com
  33. // const user2 = new User("invalid-email"); // 抛出错误: Invalid email format
复制代码

参数装饰器

参数装饰器应用于方法的参数,可以用来监视方法的参数。参数装饰器在运行时接收三个参数:

1. 对于静态成员,是类的构造函数;对于实例成员,是类的原型
2. 方法的名称
3. 参数在函数参数列表中的索引

参数装饰器的函数签名如下:
  1. function parameterDecorator(
  2.   target: Object,
  3.   propertyKey: string | symbol,
  4.   parameterIndex: number
  5. ) {
  6.   // 装饰器逻辑
  7. }
复制代码

下面是一个简单的参数装饰器示例:
  1. function logParameter(
  2.   target: Object,
  3.   propertyKey: string | symbol,
  4.   parameterIndex: number
  5. ) {
  6.   console.log(`参数装饰器被调用`);
  7.   console.log(`目标对象:`, target);
  8.   console.log(`方法名:`, propertyKey.toString());
  9.   console.log(`参数索引:`, parameterIndex);
  10. }
  11. class UserService {
  12.   createUser(
  13.     @logParameter name: string,
  14.     @logParameter age: number,
  15.     email: string
  16.   ) {
  17.     console.log(`创建用户: ${name}, ${age}, ${email}`);
  18.   }
  19. }
  20. const userService = new UserService();
  21. userService.createUser("Alice", 30, "alice@example.com");
复制代码

参数装饰器通常用于依赖注入或参数验证。下面是一个参数验证的示例:
  1. function required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
  2.   const methodName = propertyKey.toString();
  3.   const requiredParameters: number[] = Reflect.getOwnMetadata("required", target, methodName) || [];
  4.   requiredParameters.push(parameterIndex);
  5.   Reflect.defineMetadata("required", requiredParameters, target, methodName);
  6. }
  7. function validate(target: any, propertyName: string, descriptor: PropertyDescriptor) {
  8.   const method = descriptor.value;
  9.   
  10.   descriptor.value = function(...args: any[]) {
  11.     const requiredParameters: number[] = Reflect.getOwnMetadata("required", target, propertyName) || [];
  12.    
  13.     for (const parameterIndex of requiredParameters) {
  14.       if (args[parameterIndex] === undefined || args[parameterIndex] === null) {
  15.         throw new Error(`参数 ${parameterIndex} 是必需的`);
  16.       }
  17.     }
  18.    
  19.     return method.apply(this, args);
  20.   };
  21. }
  22. class OrderService {
  23.   @validate
  24.   createOrder(
  25.     @required productId: string,
  26.     @required quantity: number,
  27.     discount?: number
  28.   ) {
  29.     console.log(`创建订单: 产品ID=${productId}, 数量=${quantity}, 折扣=${discount || 0}`);
  30.   }
  31. }
  32. const orderService = new OrderService();
  33. orderService.createOrder("P123", 2); // 正确
  34. // orderService.createOrder("P123"); // 抛出错误: 参数 1 是必需的
复制代码

装饰器工厂

装饰器工厂是一个函数,它返回一个装饰器函数。装饰器工厂允许我们自定义装饰器的行为,通过传递参数来配置装饰器。

装饰器工厂的基本语法如下:
  1. function decoratorFactory(config: any) {
  2.   return function (target: any) {
  3.     // 装饰器逻辑,可以使用config
  4.   }
  5. }
  6. @decoratorFactory({ option1: true, option2: "value" })
  7. class MyClass {
  8.   // 类定义
  9. }
复制代码

下面是一个装饰器工厂的示例:
  1. function setAPIVersion(version: string) {
  2.   return function (constructor: Function) {
  3.     constructor.prototype.apiVersion = version;
  4.   };
  5. }
  6. @setAPIVersion("1.0.0")
  7. class API {
  8.   // 类定义
  9. }
  10. const api = new API();
  11. console.log((api as any).apiVersion); // 输出: 1.0.0
复制代码

装饰器工厂也可以用于方法装饰器:
  1. function logMethod(prefix: string) {
  2.   return function (
  3.     target: Object,
  4.     propertyKey: string | symbol,
  5.     descriptor: PropertyDescriptor
  6.   ) {
  7.     const originalMethod = descriptor.value;
  8.    
  9.     descriptor.value = function(...args: any[]) {
  10.       console.log(`${prefix} - 调用方法 ${propertyKey.toString()}`);
  11.       const result = originalMethod.apply(this, args);
  12.       console.log(`${prefix} - 方法 ${propertyKey.toString()} 完成`);
  13.       return result;
  14.     };
  15.    
  16.     return descriptor;
  17.   };
  18. }
  19. class DataService {
  20.   @logMethod("[Data Service]")
  21.   fetchData(id: string) {
  22.     console.log(`获取数据,ID: ${id}`);
  23.     return { id, data: "示例数据" };
  24.   }
  25. }
  26. const dataService = new DataService();
  27. const data = dataService.fetchData("123");
  28. // 输出:
  29. // [Data Service] - 调用方法 fetchData
  30. // 获取数据,ID: 123
  31. // [Data Service] - 方法 fetchData 完成
复制代码

实际应用场景

装饰器在实际开发中有许多应用场景,下面介绍几个常见的例子。

1. 日志记录

装饰器可以用于自动记录方法的调用和返回值:
  1. function log(
  2.   target: Object,
  3.   propertyKey: string | symbol,
  4.   descriptor: PropertyDescriptor
  5. ) {
  6.   const originalMethod = descriptor.value;
  7.   
  8.   descriptor.value = function(...args: any[]) {
  9.     const start = Date.now();
  10.     console.log(`[${new Date().toISOString()}] 调用方法 ${propertyKey.toString()},参数: ${JSON.stringify(args)}`);
  11.    
  12.     try {
  13.       const result = originalMethod.apply(this, args);
  14.       const duration = Date.now() - start;
  15.       console.log(`[${new Date().toISOString()}] 方法 ${propertyKey.toString()} 成功完成,耗时: ${duration}ms`);
  16.       return result;
  17.     } catch (error) {
  18.       const duration = Date.now() - start;
  19.       console.error(`[${new Date().toISOString()}] 方法 ${propertyKey.toString()} 抛出异常,耗时: ${duration}ms,错误: ${error}`);
  20.       throw error;
  21.     }
  22.   };
  23.   
  24.   return descriptor;
  25. }
  26. class ProductService {
  27.   @log
  28.   getProduct(id: string) {
  29.     // 模拟数据库查询
  30.     if (id === "invalid") {
  31.       throw new Error("产品不存在");
  32.     }
  33.     return { id, name: "示例产品", price: 99.99 };
  34.   }
  35. }
  36. const productService = new ProductService();
  37. productService.getProduct("123"); // 记录成功调用
  38. try {
  39.   productService.getProduct("invalid"); // 记录异常
  40. } catch (error) {
  41.   console.log("捕获到异常:", error.message);
  42. }
复制代码

2. 权限控制

装饰器可以用于检查用户权限:
  1. enum Role {
  2.   ADMIN = "admin",
  3.   USER = "user",
  4.   GUEST = "guest"
  5. }
  6. function hasRole(role: Role) {
  7.   return function (
  8.     target: Object,
  9.     propertyKey: string | symbol,
  10.     descriptor: PropertyDescriptor
  11.   ) {
  12.     const originalMethod = descriptor.value;
  13.    
  14.     descriptor.value = function(...args: any[]) {
  15.       // 假设用户角色存储在实例的userRole属性中
  16.       const userRole = (this as any).userRole;
  17.       
  18.       if (userRole !== role) {
  19.         throw new Error(`权限不足,需要 ${role} 权限`);
  20.       }
  21.       
  22.       return originalMethod.apply(this, args);
  23.     };
  24.    
  25.     return descriptor;
  26.   };
  27. }
  28. class AdminService {
  29.   userRole: Role;
  30.   
  31.   constructor(role: Role) {
  32.     this.userRole = role;
  33.   }
  34.   
  35.   @hasRole(Role.ADMIN)
  36.   deleteUser(userId: string) {
  37.     console.log(`删除用户: ${userId}`);
  38.     return { success: true };
  39.   }
  40. }
  41. const adminService = new AdminService(Role.ADMIN);
  42. adminService.deleteUser("123"); // 成功
  43. const userService = new AdminService(Role.USER);
  44. try {
  45.   userService.deleteUser("456"); // 抛出错误: 权限不足,需要 admin 权限
  46. } catch (error) {
  47.   console.log("捕获到异常:", error.message);
  48. }
复制代码

3. 性能监控

装饰器可以用于监控方法的执行时间:
  1. function measure(
  2.   target: Object,
  3.   propertyKey: string | symbol,
  4.   descriptor: PropertyDescriptor
  5. ) {
  6.   const originalMethod = descriptor.value;
  7.   
  8.   descriptor.value = function(...args: any[]) {
  9.     const start = performance.now();
  10.     const result = originalMethod.apply(this, args);
  11.     const end = performance.now();
  12.     const duration = end - start;
  13.    
  14.     console.log(`方法 ${propertyKey.toString()} 执行时间: ${duration.toFixed(2)}ms`);
  15.    
  16.     // 如果方法返回Promise,等待Promise完成
  17.     if (result && typeof result.then === 'function') {
  18.       return result.then((res: any) => {
  19.         const asyncEnd = performance.now();
  20.         const asyncDuration = asyncEnd - start;
  21.         console.log(`方法 ${propertyKey.toString()} 总执行时间 (异步): ${asyncDuration.toFixed(2)}ms`);
  22.         return res;
  23.       });
  24.     }
  25.    
  26.     return result;
  27.   };
  28.   
  29.   return descriptor;
  30. }
  31. class DataProcessor {
  32.   @measure
  33.   processSync(data: any[]) {
  34.     // 模拟同步处理
  35.     let result = 0;
  36.     for (let i = 0; i < 1000000; i++) {
  37.       result += Math.sqrt(i);
  38.     }
  39.     return { processed: true, count: data.length };
  40.   }
  41.   
  42.   @measure
  43.   async processAsync(data: any[]) {
  44.     // 模拟异步处理
  45.     return new Promise(resolve => {
  46.       setTimeout(() => {
  47.         resolve({ processed: true, count: data.length });
  48.       }, 100);
  49.     });
  50.   }
  51. }
  52. const processor = new DataProcessor();
  53. processor.processSync([1, 2, 3]);
  54. processor.processAsync([4, 5, 6]);
复制代码

4. 缓存

装饰器可以用于缓存方法的结果,避免重复计算:
  1. function cache(
  2.   target: Object,
  3.   propertyKey: string | symbol,
  4.   descriptor: PropertyDescriptor
  5. ) {
  6.   const originalMethod = descriptor.value;
  7.   const cache = new Map<string, any>();
  8.   
  9.   descriptor.value = function(...args: any[]) {
  10.     const key = JSON.stringify(args);
  11.    
  12.     if (cache.has(key)) {
  13.       console.log(`从缓存中获取 ${propertyKey.toString()}(${key})`);
  14.       return cache.get(key);
  15.     }
  16.    
  17.     console.log(`计算 ${propertyKey.toString()}(${key})`);
  18.     const result = originalMethod.apply(this, args);
  19.     cache.set(key, result);
  20.     return result;
  21.   };
  22.   
  23.   return descriptor;
  24. }
  25. class MathService {
  26.   @cache
  27.   fibonacci(n: number): number {
  28.     if (n <= 1) {
  29.       return n;
  30.     }
  31.     return this.fibonacci(n - 1) + this.fibonacci(n - 2);
  32.   }
  33. }
  34. const mathService = new MathService();
  35. console.log(mathService.fibonacci(10)); // 计算并缓存
  36. console.log(mathService.fibonacci(10)); // 从缓存中获取
  37. console.log(mathService.fibonacci(15)); // 计算并缓存
  38. console.log(mathService.fibonacci(10)); // 从缓存中获取
复制代码

5. 防抖和节流

装饰器可以用于实现防抖和节流功能:
  1. function debounce(delay: number) {
  2.   return function (
  3.     target: Object,
  4.     propertyKey: string | symbol,
  5.     descriptor: PropertyDescriptor
  6.   ) {
  7.     const originalMethod = descriptor.value;
  8.     let timeoutId: NodeJS.Timeout | null = null;
  9.    
  10.     descriptor.value = function(...args: any[]) {
  11.       if (timeoutId) {
  12.         clearTimeout(timeoutId);
  13.       }
  14.       
  15.       timeoutId = setTimeout(() => {
  16.         originalMethod.apply(this, args);
  17.         timeoutId = null;
  18.       }, delay);
  19.     };
  20.    
  21.     return descriptor;
  22.   };
  23. }
  24. function throttle(delay: number) {
  25.   return function (
  26.     target: Object,
  27.     propertyKey: string | symbol,
  28.     descriptor: PropertyDescriptor
  29.   ) {
  30.     const originalMethod = descriptor.value;
  31.     let lastCall = 0;
  32.    
  33.     descriptor.value = function(...args: any[]) {
  34.       const now = Date.now();
  35.       
  36.       if (now - lastCall >= delay) {
  37.         lastCall = now;
  38.         originalMethod.apply(this, args);
  39.       }
  40.     };
  41.    
  42.     return descriptor;
  43.   };
  44. }
  45. class SearchService {
  46.   @debounce(300)
  47.   search(query: string) {
  48.     console.log(`搜索: ${query}`);
  49.     // 实际应用中,这里会调用API进行搜索
  50.   }
  51.   
  52.   @throttle(1000)
  53.   logActivity(activity: string) {
  54.     console.log(`记录活动: ${activity}`);
  55.     // 实际应用中,这里会发送日志到服务器
  56.   }
  57. }
  58. const searchService = new SearchService();
  59. searchService.search("a");
  60. searchService.search("ap");
  61. searchService.search("app");
  62. searchService.search("appl");
  63. searchService.search("apple"); // 只有最后一次搜索会执行
  64. const activityService = new SearchService();
  65. activityService.logActivity("点击按钮");
  66. activityService.logActivity("滚动页面");
  67. activityService.logActivity("输入文本"); // 只有第一次和最后一次活动会记录
复制代码

装饰器元数据

装饰器元数据是TypeScript装饰器的一个高级特性,它允许我们在装饰器中存储和检索额外的信息。要使用元数据,需要安装reflect-metadata库并在应用中导入它:
  1. npm install reflect-metadata
复制代码

然后在应用的入口文件中导入:
  1. import "reflect-metadata";
复制代码

元数据API提供了几个方法来操作元数据:

• Reflect.defineMetadata(metadataKey, metadataValue, target)
• Reflect.getMetadata(metadataKey, target)
• Reflect.hasMetadata(metadataKey, target)
• Reflect.deleteMetadata(metadataKey, target)
• Reflect.getMetadataKeys(target)
• Reflect.getOwnMetadata(metadataKey, target)
• Reflect.getOwnMetadataKeys(target)

下面是一个使用元数据的示例:
  1. function classDecorator(constructor: Function) {
  2.   Reflect.defineMetadata("class:version", "1.0.0", constructor);
  3. }
  4. function methodDecorator(target: Object, propertyKey: string | symbol) {
  5.   Reflect.defineMetadata("method:permission", "admin", target, propertyKey);
  6. }
  7. @classDecorator
  8. class UserService {
  9.   @methodDecorator
  10.   createUser() {
  11.     console.log("创建用户");
  12.   }
  13. }
  14. // 获取类元数据
  15. const version = Reflect.getMetadata("class:version", UserService);
  16. console.log(`类版本: ${version}`); // 输出: 类版本: 1.0.0
  17. // 获取方法元数据
  18. const permission = Reflect.getMetadata("method:permission", UserService.prototype, "createUser");
  19. console.log(`方法权限: ${permission}`); // 输出: 方法权限: admin
复制代码

元数据在依赖注入框架中特别有用,例如:
  1. function Injectable() {
  2.   return function (constructor: Function) {
  3.     Reflect.defineMetadata("injectable", true, constructor);
  4.   };
  5. }
  6. function Inject(token: any) {
  7.   return function (target: Object, propertyKey: string | symbol) {
  8.     Reflect.defineMetadata("inject:token", token, target, propertyKey);
  9.   };
  10. }
  11. @Injectable()
  12. class DatabaseService {
  13.   query(sql: string) {
  14.     console.log(`执行查询: ${sql}`);
  15.     return [{ id: 1, name: "示例数据" }];
  16.   }
  17. }
  18. @Injectable()
  19. class UserService {
  20.   @Inject(DatabaseService)
  21.   private database!: DatabaseService;
  22.   
  23.   getUsers() {
  24.     return this.database.query("SELECT * FROM users");
  25.   }
  26. }
  27. // 模拟依赖注入容器
  28. class Container {
  29.   private services = new Map<string, any>();
  30.   
  31.   register<T>(token: new (...args: any[]) => T, instance: T) {
  32.     this.services.set(token.name, instance);
  33.   }
  34.   
  35.   resolve<T>(target: new (...args: any[]) => T): T {
  36.     const isInjectable = Reflect.getMetadata("injectable", target);
  37.     if (!isInjectable) {
  38.       throw new Error(`${target.name} 不是可注入的`);
  39.     }
  40.    
  41.     const instance = new target();
  42.    
  43.     // 获取所有属性
  44.     const properties = Object.getOwnPropertyNames(target.prototype);
  45.    
  46.     for (const property of properties) {
  47.       const injectToken = Reflect.getMetadata("inject:token", target.prototype, property);
  48.       if (injectToken) {
  49.         const dependency = this.services.get(injectToken.name);
  50.         if (!dependency) {
  51.           throw new Error(`依赖 ${injectToken.name} 未注册`);
  52.         }
  53.         (instance as any)[property] = dependency;
  54.       }
  55.     }
  56.    
  57.     return instance;
  58.   }
  59. }
  60. // 使用容器
  61. const container = new Container();
  62. container.register(DatabaseService, new DatabaseService());
  63. const userService = container.resolve(UserService);
  64. const users = userService.getUsers();
  65. console.log(users);
复制代码

最佳实践和注意事项

在使用装饰器时,有一些最佳实践和注意事项需要牢记:

1. 装饰器执行顺序

多个装饰器应用于同一声明时,它们的执行顺序如下:

• 对于类装饰器,从下到上执行
• 对于方法/属性/参数装饰器,从左到右执行
• 方法装饰器中,参数装饰器先于方法装饰器执行

示例:
  1. function classDecorator1(constructor: Function) {
  2.   console.log("类装饰器1");
  3. }
  4. function classDecorator2(constructor: Function) {
  5.   console.log("类装饰器2");
  6. }
  7. function methodDecorator1(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  8.   console.log("方法装饰器1");
  9. }
  10. function methodDecorator2(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  11.   console.log("方法装饰器2");
  12. }
  13. function paramDecorator1(target: Object, propertyKey: string, parameterIndex: number) {
  14.   console.log("参数装饰器1");
  15. }
  16. function paramDecorator2(target: Object, propertyKey: string, parameterIndex: number) {
  17.   console.log("参数装饰器2");
  18. }
  19. @classDecorator1
  20. @classDecorator2
  21. class ExampleClass {
  22.   @methodDecorator1
  23.   @methodDecorator2
  24.   method(
  25.     @paramDecorator1 param1: string,
  26.     @paramDecorator2 param2: number
  27.   ) {
  28.     // 方法实现
  29.   }
  30. }
  31. // 输出:
  32. // 参数装饰器2
  33. // 参数装饰器1
  34. // 方法装饰器2
  35. // 方法装饰器1
  36. // 类装饰器2
  37. // 类装饰器1
复制代码

2. 装饰器的性能考虑

装饰器可能会影响应用的性能,特别是在类实例化时。以下是一些优化建议:

• 避免在装饰器中执行重量级操作
• 考虑使用懒加载模式,只在需要时执行装饰器逻辑
• 对于频繁调用的方法,谨慎使用装饰器

示例:
  1. // 不好的做法 - 每次实例化时都会执行重量级操作
  2. function heavyDecorator(constructor: Function) {
  3.   // 模拟重量级操作
  4.   for (let i = 0; i < 1000000; i++) {
  5.     Math.sqrt(i);
  6.   }
  7.   console.log("重量级装饰器执行完成");
  8. }
  9. @heavyDecorator
  10. class BadExample {
  11.   // 类定义
  12. }
  13. // 好的做法 - 使用懒加载
  14. function lazyHeavyDecorator(constructor: Function) {
  15.   let initialized = false;
  16.   
  17.   return class extends constructor {
  18.     constructor(...args: any[]) {
  19.       super(...args);
  20.       
  21.       if (!initialized) {
  22.         // 模拟重量级操作
  23.         for (let i = 0; i < 1000000; i++) {
  24.           Math.sqrt(i);
  25.         }
  26.         console.log("懒加载重量级装饰器执行完成");
  27.         initialized = true;
  28.       }
  29.     }
  30.   };
  31. }
  32. @lazyHeavyDecorator
  33. class GoodExample {
  34.   // 类定义
  35. }
  36. console.log("创建第一个实例");
  37. const good1 = new GoodExample(); // 执行重量级操作
  38. console.log("创建第二个实例");
  39. const good2 = new GoodExample(); // 不执行重量级操作
复制代码

3. 装饰器的可测试性

装饰器可能会使代码更难测试,以下是一些提高可测试性的建议:

• 将装饰器逻辑与业务逻辑分离
• 使用依赖注入来提供装饰器所需的依赖
• 为装饰器编写单元测试

示例:
  1. // 不好的做法 - 装饰器与业务逻辑紧密耦合
  2. function logBad(target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  3.   const originalMethod = descriptor.value;
  4.   
  5.   descriptor.value = function(...args: any[]) {
  6.     console.log(`调用方法 ${propertyKey}`);
  7.     return originalMethod.apply(this, args);
  8.   };
  9.   
  10.   return descriptor;
  11. }
  12. class BadExample {
  13.   @logBad
  14.   doSomething() {
  15.     // 业务逻辑
  16.   }
  17. }
  18. // 好的做法 - 装饰器与业务逻辑分离
  19. interface Logger {
  20.   log(message: string): void;
  21. }
  22. class ConsoleLogger implements Logger {
  23.   log(message: string): void {
  24.     console.log(message);
  25.   }
  26. }
  27. function logGood(logger: Logger) {
  28.   return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) {
  29.     const originalMethod = descriptor.value;
  30.    
  31.     descriptor.value = function(...args: any[]) {
  32.       logger.log(`调用方法 ${propertyKey}`);
  33.       return originalMethod.apply(this, args);
  34.     };
  35.    
  36.     return descriptor;
  37.   };
  38. }
  39. class GoodExample {
  40.   constructor(private logger: Logger) {}
  41.   
  42.   @logGood(new ConsoleLogger())
  43.   doSomething() {
  44.     // 业务逻辑
  45.   }
  46. }
  47. // 测试装饰器
  48. class TestLogger implements Logger {
  49.   messages: string[] = [];
  50.   
  51.   log(message: string): void {
  52.     this.messages.push(message);
  53.   }
  54. }
  55. describe("logGood装饰器", () => {
  56.   it("应该记录方法调用", () => {
  57.     const testLogger = new TestLogger();
  58.    
  59.     // 创建一个测试类并应用装饰器
  60.     class TestClass {
  61.       @logGood(testLogger)
  62.       testMethod() {
  63.         return "test result";
  64.       }
  65.     }
  66.    
  67.     const instance = new TestClass();
  68.     const result = instance.testMethod();
  69.    
  70.     expect(result).toBe("test result");
  71.     expect(testLogger.messages).toContain("调用方法 testMethod");
  72.   });
  73. });
复制代码

4. 装饰器的错误处理

装饰器中的错误可能会导致应用崩溃,以下是一些错误处理的建议:

• 在装饰器中添加适当的错误处理
• 考虑使用装饰器工厂来提供配置选项
• 记录装饰器中的错误以便调试

示例:
  1. function safeMethodDecorator(
  2.   target: Object,
  3.   propertyKey: string,
  4.   descriptor: PropertyDescriptor
  5. ) {
  6.   const originalMethod = descriptor.value;
  7.   
  8.   descriptor.value = function(...args: any[]) {
  9.     try {
  10.       return originalMethod.apply(this, args);
  11.     } catch (error) {
  12.       console.error(`方法 ${propertyKey} 执行出错:`, error);
  13.       // 可以选择重新抛出错误或返回默认值
  14.       throw error;
  15.     }
  16.   };
  17.   
  18.   return descriptor;
  19. }
  20. function configurableMethodDecorator(options: { logErrors: boolean, defaultValue?: any }) {
  21.   return function (
  22.     target: Object,
  23.     propertyKey: string,
  24.     descriptor: PropertyDescriptor
  25.   ) {
  26.     const originalMethod = descriptor.value;
  27.    
  28.     descriptor.value = function(...args: any[]) {
  29.       try {
  30.         return originalMethod.apply(this, args);
  31.       } catch (error) {
  32.         if (options.logErrors) {
  33.           console.error(`方法 ${propertyKey} 执行出错:`, error);
  34.         }
  35.         
  36.         if (options.defaultValue !== undefined) {
  37.           return options.defaultValue;
  38.         }
  39.         
  40.         throw error;
  41.       }
  42.     };
  43.    
  44.     return descriptor;
  45.   };
  46. }
  47. class Example {
  48.   @safeMethodDecorator
  49.   method1() {
  50.     throw new Error("测试错误");
  51.   }
  52.   
  53.   @configurableMethodDecorator({ logErrors: true, defaultValue: "默认值" })
  54.   method2() {
  55.     throw new Error("测试错误");
  56.   }
  57. }
  58. const example = new Example();
  59. try {
  60.   example.method1();
  61. } catch (error) {
  62.   console.log("捕获到错误:", error.message);
  63. }
  64. console.log(example.method2()); // 输出: 默认值
复制代码

5. 装饰器的类型安全

TypeScript的装饰器是实验性特性,类型支持有限。以下是一些提高类型安全的建议:

• 使用泛型来增强装饰器的类型安全性
• 为装饰器参数定义明确的类型
• 使用类型断言和类型守卫来处理类型不确定性

示例:
  1. // 不好的做法 - 缺乏类型安全
  2. function badDecorator(target: any, key: any, descriptor: any) {
  3.   // 装饰器逻辑
  4. }
  5. // 好的做法 - 使用泛型和明确的类型
  6. function goodDecorator<T>(
  7.   target: T,
  8.   propertyKey: keyof T,
  9.   descriptor: TypedPropertyDescriptor<(...args: any[]) => any>
  10. ) {
  11.   // 装饰器逻辑
  12. }
  13. // 更好的做法 - 为装饰器参数定义类型
  14. interface ValidationOptions {
  15.   required?: boolean;
  16.   minLength?: number;
  17.   pattern?: RegExp;
  18. }
  19. function validateProperty(options: ValidationOptions) {
  20.   return function <T extends Record<string, any>>(
  21.     target: T,
  22.     propertyKey: keyof T
  23.   ) {
  24.     let value: any;
  25.    
  26.     const getter = function() {
  27.       return value;
  28.     };
  29.    
  30.     const setter = function(newVal: any) {
  31.       if (options.required && (newVal === undefined || newVal === null)) {
  32.         throw new Error(`${String(propertyKey)} 是必需的`);
  33.       }
  34.       
  35.       if (options.minLength && typeof newVal === 'string' && newVal.length < options.minLength) {
  36.         throw new Error(`${String(propertyKey)} 的最小长度是 ${options.minLength}`);
  37.       }
  38.       
  39.       if (options.pattern && typeof newVal === 'string' && !options.pattern.test(newVal)) {
  40.         throw new Error(`${String(propertyKey)} 不符合要求的格式`);
  41.       }
  42.       
  43.       value = newVal;
  44.     };
  45.    
  46.     Object.defineProperty(target, propertyKey, {
  47.       get: getter,
  48.       set: setter,
  49.       enumerable: true,
  50.       configurable: true
  51.     });
  52.   };
  53. }
  54. class User {
  55.   @validateProperty({ required: true, minLength: 3 })
  56.   name!: string;
  57.   
  58.   @validateProperty({ required: true, pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/ })
  59.   email!: string;
  60. }
  61. const user = new User();
  62. user.name = "Alice"; // 正确
  63. user.email = "alice@example.com"; // 正确
  64. try {
  65.   user.name = "A"; // 抛出错误: name 的最小长度是 3
  66. } catch (error) {
  67.   console.log("捕获到错误:", error.message);
  68. }
  69. try {
  70.   user.email = "invalid-email"; // 抛出错误: email 不符合要求的格式
  71. } catch (error) {
  72.   console.log("捕获到错误:", error.message);
  73. }
复制代码

总结

TypeScript装饰器是一种强大的特性,它允许我们在不修改原有代码的情况下,动态地扩展类的功能。通过装饰器,我们可以实现代码的复用和关注点分离,使代码更加清晰、可维护。

在本指南中,我们详细介绍了TypeScript装饰器的各个方面:

1. 装饰器基础:了解了装饰器的基本概念和语法
2. 类装饰器:学习了如何使用装饰器扩展或修改类的行为
3. 方法装饰器:探索了如何监视、修改或替换方法定义
4. 属性装饰器:掌握了如何监视或修改属性的定义
5. 参数装饰器:了解了如何监视方法的参数
6. 装饰器工厂:学习了如何创建可配置的装饰器
7. 实际应用场景:探讨了装饰器在日志记录、权限控制、性能监控、缓存和防抖节流等方面的应用
8. 装饰器元数据:了解了如何使用反射和元数据来增强装饰器的功能
9. 最佳实践和注意事项:学习了如何有效地使用装饰器,避免常见的陷阱

通过掌握TypeScript装饰器,你可以编写更加优雅、可维护和可扩展的代码。装饰器特别适用于框架开发、AOP(面向切面编程)和依赖注入等场景。希望本指南能够帮助你充分利用TypeScript装饰器的强大特性,让你的代码焕然一新。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关闭

站长推荐上一条 /1 下一条

手机版|联系我们|小黑屋|TG频道|RSS |网站地图

Powered by Pixtech

© 2025-2026 Pixtech Team.

>