简体中文 繁體中文 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万

主题

1174

科技点

3万

积分

白金月票

碾压王

积分
32796

立华奏

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

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

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

x
引言

TypeScript作为JavaScript的超集,为开发者提供了静态类型检查和更强大的面向对象编程能力。在TypeScript的众多特性中,装饰器(Decorators)是一个极具潜力的元编程特性,它允许我们在不修改原有代码结构的情况下,动态地为类、方法、属性或参数添加额外的行为。装饰器不仅可以提高代码的可读性和可维护性,还能显著提升开发效率,是现代TypeScript项目中不可或缺的利器。

本文将深入探讨TypeScript装饰器的核心概念、实际应用场景以及实战技巧,帮助开发者充分利用装饰器来提升代码质量与开发效率。

TypeScript装饰器基础

什么是装饰器

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

本质上,装饰器是一个函数,它接收目标对象(类、方法、属性等)作为参数,并可以对其进行修改或扩展。这种模式被称为元编程,即编写可以操作程序的程序。

装饰器的工作原理

装饰器在TypeScript中的工作原理可以简化为以下几个步骤:

1. 定义装饰器函数
2. 使用@符号将装饰器应用到目标上
3. 在编译或运行时,装饰器函数会被调用,并接收相关信息作为参数
4. 装饰器函数可以修改或扩展目标的行为

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

装饰器的类型

TypeScript支持多种类型的装饰器,每种类型都有其特定的应用场景和参数结构。

类装饰器

类装饰器应用于类构造函数,可以用来监视、修改或替换类定义。类装饰器接收一个参数:类的构造函数。
  1. function classDecorator<T extends { new(...args: any[]): {} }>(constructor: T) {
  2.   return class extends constructor {
  3.     newProperty = "new property";
  4.     hello = "override";
  5.   }
  6. }
  7. @classDecorator
  8. class Greeter {
  9.   property = "property";
  10.   hello: string;
  11.   constructor(m: string) {
  12.     this.hello = m;
  13.   }
  14. }
  15. console.log(new Greeter("world"));
复制代码

方法装饰器

方法装饰器应用于方法定义上,可以用来监视、修改或替换方法定义。方法装饰器接收三个参数:

• 对于静态成员,是类的构造函数;对于实例成员,是类的原型
• 成员的名字
• 成员的属性描述符
  1. function methodDecorator(
  2.   target: any,
  3.   propertyKey: string,
  4.   descriptor: PropertyDescriptor
  5. ) {
  6.   const originalMethod = descriptor.value;
  7.   
  8.   descriptor.value = function(...args: any[]) {
  9.     console.log(`Method ${propertyKey} called with args: ${args}`);
  10.     const result = originalMethod.apply(this, args);
  11.     console.log(`Method ${propertyKey} returned: ${result}`);
  12.     return result;
  13.   };
  14.   
  15.   return descriptor;
  16. }
  17. class Calculator {
  18.   @methodDecorator
  19.   add(a: number, b: number): number {
  20.     return a + b;
  21.   }
  22. }
  23. const calculator = new Calculator();
  24. calculator.add(2, 3);
复制代码

属性装饰器

属性装饰器应用于属性声明上,可以用来监视或修改属性的定义。属性装饰器接收两个参数:

• 对于静态成员,是类的构造函数;对于实例成员,是类的原型
• 成员的名字
  1. function propertyDecorator(target: any, propertyKey: string) {
  2.   let value: string;
  3.   
  4.   const getter = function() {
  5.     console.log(`Getting value for ${propertyKey}`);
  6.     return value;
  7.   };
  8.   
  9.   const setter = function(newVal: string) {
  10.     console.log(`Setting value for ${propertyKey} to ${newVal}`);
  11.     value = newVal;
  12.   };
  13.   
  14.   Object.defineProperty(target, propertyKey, {
  15.     get: getter,
  16.     set: setter,
  17.     enumerable: true,
  18.     configurable: true
  19.   });
  20. }
  21. class User {
  22.   @propertyDecorator
  23.   public name: string;
  24.   
  25.   constructor(name: string) {
  26.     this.name = name;
  27.   }
  28. }
  29. const user = new User("John Doe");
  30. console.log(user.name);
  31. user.name = "Jane Smith";
复制代码

参数装饰器

参数装饰器应用于参数声明上,可以用来监视参数的定义。参数装饰器接收三个参数:

• 对于静态成员,是类的构造函数;对于实例成员,是类的原型
• 成员的名字
• 参数在函数参数列表中的索引
  1. function parameterDecorator(target: any, propertyKey: string, parameterIndex: number) {
  2.   console.log(`Parameter decorator applied to ${propertyKey} at index ${parameterIndex}`);
  3. }
  4. class UserService {
  5.   getUserById(@parameterDecorator id: number) {
  6.     console.log(`Fetching user with ID: ${id}`);
  7.     return { id, name: "John Doe" };
  8.   }
  9. }
  10. const userService = new UserService();
  11. userService.getUserById(123);
复制代码

实际项目应用场景

装饰器在实际项目中有广泛的应用场景,下面我们将探讨几个常见的应用场景,并提供详细的代码示例。

日志记录

日志记录是应用程序开发中的重要环节,装饰器可以优雅地实现日志功能,而不需要在业务逻辑中掺杂日志代码。
  1. function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  2.   const originalMethod = descriptor.value;
  3.   
  4.   descriptor.value = function(...args: any[]) {
  5.     const startTime = Date.now();
  6.     console.log(`[${new Date().toISOString()}] Calling ${propertyKey} with args:`, args);
  7.    
  8.     try {
  9.       const result = originalMethod.apply(this, args);
  10.       const endTime = Date.now();
  11.       console.log(`[${new Date().toISOString()}] Method ${propertyKey} completed in ${endTime - startTime}ms`);
  12.       return result;
  13.     } catch (error) {
  14.       console.error(`[${new Date().toISOString()}] Method ${propertyKey} threw error:`, error);
  15.       throw error;
  16.     }
  17.   };
  18.   
  19.   return descriptor;
  20. }
  21. class ProductService {
  22.   @log
  23.   getProduct(id: number) {
  24.     // 模拟数据库查询
  25.     if (id <= 0) {
  26.       throw new Error("Invalid product ID");
  27.     }
  28.     return { id, name: `Product ${id}`, price: 100 };
  29.   }
  30.   
  31.   @log
  32.   saveProduct(product: any) {
  33.     // 模拟保存产品
  34.     console.log("Saving product to database...");
  35.     return { ...product, id: Math.floor(Math.random() * 1000) };
  36.   }
  37. }
  38. const productService = new ProductService();
  39. productService.getProduct(123);
  40. productService.saveProduct({ name: "New Product", price: 200 });
  41. try {
  42.   productService.getProduct(-1);
  43. } catch (error) {
  44.   console.log("Caught expected error:", error.message);
  45. }
复制代码

权限控制

在许多应用中,需要对某些方法或端点进行权限控制,装饰器可以很方便地实现这一功能。
  1. // 定义角色类型
  2. type Role = 'admin' | 'user' | 'guest';
  3. // 用户上下文
  4. interface UserContext {
  5.   id: number;
  6.   username: string;
  7.   roles: Role[];
  8. }
  9. // 当前用户上下文(在实际应用中可能来自JWT或会话)
  10. const currentUser: UserContext = {
  11.   id: 1,
  12.   username: 'johndoe',
  13.   roles: ['user']
  14. };
  15. // 权限装饰器工厂
  16. function requireRole(...requiredRoles: Role[]) {
  17.   return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  18.     const originalMethod = descriptor.value;
  19.    
  20.     descriptor.value = function(...args: any[]) {
  21.       // 检查用户是否有所需角色
  22.       const hasRequiredRole = requiredRoles.some(role =>
  23.         currentUser.roles.includes(role)
  24.       );
  25.       
  26.       if (!hasRequiredRole) {
  27.         throw new Error(`Access denied. Required roles: ${requiredRoles.join(', ')}`);
  28.       }
  29.       
  30.       return originalMethod.apply(this, args);
  31.     };
  32.    
  33.     return descriptor;
  34.   };
  35. }
  36. class AdminService {
  37.   @requireRole('admin')
  38.   deleteUser(userId: number) {
  39.     console.log(`User ${userId} deleted by admin`);
  40.     return { success: true };
  41.   }
  42.   
  43.   @requireRole('admin', 'user')
  44.   updateUserProfile(userId: number, profile: any) {
  45.     console.log(`User ${userId} profile updated`);
  46.     return { success: true };
  47.   }
  48.   
  49.   @requireRole('guest')
  50.   viewPublicContent() {
  51.     console.log("Displaying public content");
  52.     return { content: "This is public content" };
  53.   }
  54. }
  55. const adminService = new AdminService();
  56. // 这些调用会成功,因为当前用户有'user'角色
  57. try {
  58.   adminService.updateUserProfile(123, { name: "John" });
  59.   adminService.viewPublicContent();
  60. } catch (error) {
  61.   console.error("Error:", error.message);
  62. }
  63. // 这个调用会失败,因为当前用户没有'admin'角色
  64. try {
  65.   adminService.deleteUser(456);
  66. } catch (error) {
  67.   console.error("Error:", error.message);
  68. }
复制代码

数据验证

装饰器可以用于实现优雅的数据验证,特别是在处理API请求或表单数据时。
  1. // 定义验证规则接口
  2. interface ValidationRule {
  3.   validate(value: any): boolean;
  4.   message: string;
  5. }
  6. // 必填验证规则
  7. const required: ValidationRule = {
  8.   validate: (value) => value !== undefined && value !== null && value !== '',
  9.   message: 'This field is required'
  10. };
  11. // 最小长度验证规则
  12. function minLength(min: number): ValidationRule {
  13.   return {
  14.     validate: (value) => value.length >= min,
  15.     message: `Minimum length is ${min}`
  16.   };
  17. }
  18. // 邮箱格式验证规则
  19. const email: ValidationRule = {
  20.   validate: (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
  21.   message: 'Invalid email format'
  22. };
  23. // 验证装饰器工厂
  24. function validate(...rules: ValidationRule[]) {
  25.   return function(target: any, propertyKey: string) {
  26.     let value: any;
  27.    
  28.     const getter = function() {
  29.       return value;
  30.     };
  31.    
  32.     const setter = function(newVal: any) {
  33.       for (const rule of rules) {
  34.         if (!rule.validate(newVal)) {
  35.           throw new Error(`Validation failed for ${propertyKey}: ${rule.message}`);
  36.         }
  37.       }
  38.       value = newVal;
  39.     };
  40.    
  41.     Object.defineProperty(target, propertyKey, {
  42.       get: getter,
  43.       set: setter,
  44.       enumerable: true,
  45.       configurable: true
  46.     });
  47.   };
  48. }
  49. class UserRegistration {
  50.   @validate(required, minLength(3))
  51.   username: string;
  52.   
  53.   @validate(required, email)
  54.   email: string;
  55.   
  56.   @validate(required, minLength(8))
  57.   password: string;
  58.   
  59.   constructor(username: string, email: string, password: string) {
  60.     this.username = username;
  61.     this.email = email;
  62.     this.password = password;
  63.   }
  64. }
  65. // 正确的注册
  66. try {
  67.   const user1 = new UserRegistration("john_doe", "john@example.com", "password123");
  68.   console.log("User registration successful:", user1);
  69. } catch (error) {
  70.   console.error("Registration error:", error.message);
  71. }
  72. // 错误的注册 - 用户名太短
  73. try {
  74.   const user2 = new UserRegistration("jd", "john@example.com", "password123");
  75. } catch (error) {
  76.   console.error("Registration error:", error.message);
  77. }
  78. // 错误的注册 - 无效邮箱
  79. try {
  80.   const user3 = new UserRegistration("john_doe", "invalid-email", "password123");
  81. } catch (error) {
  82.   console.error("Registration error:", error.message);
  83. }
复制代码

依赖注入

依赖注入是现代软件开发中的一种重要设计模式,装饰器在实现依赖注入时非常有用。
  1. // 依赖容器
  2. class Container {
  3.   private services: Map<string, any> = new Map();
  4.   
  5.   register<T>(name: string, implementation: new (...args: any[]) => T): void {
  6.     this.services.set(name, implementation);
  7.   }
  8.   
  9.   resolve<T>(name: string): T {
  10.     const implementation = this.services.get(name);
  11.     if (!implementation) {
  12.       throw new Error(`Service ${name} not found`);
  13.     }
  14.     return new implementation();
  15.   }
  16. }
  17. // 全局容器实例
  18. const container = new Container();
  19. // 依赖注入装饰器
  20. function inject(serviceName: string) {
  21.   return function(target: any, propertyKey: string) {
  22.     Object.defineProperty(target, propertyKey, {
  23.       get() {
  24.         return container.resolve(serviceName);
  25.       },
  26.       enumerable: true,
  27.       configurable: true
  28.     });
  29.   };
  30. }
  31. // 定义服务
  32. class LoggerService {
  33.   log(message: string) {
  34.     console.log(`[LOG] ${new Date().toISOString()}: ${message}`);
  35.   }
  36. }
  37. class DatabaseService {
  38.   query(sql: string) {
  39.     console.log(`Executing query: ${sql}`);
  40.     return [{ id: 1, name: "Sample Data" }];
  41.   }
  42. }
  43. // 注册服务
  44. container.register('logger', LoggerService);
  45. container.register('database', DatabaseService);
  46. // 使用依赖注入
  47. class UserService {
  48.   @inject('logger')
  49.   private logger!: LoggerService;
  50.   
  51.   @inject('database')
  52.   private database!: DatabaseService;
  53.   
  54.   getUser(id: number) {
  55.     this.logger.log(`Fetching user with ID: ${id}`);
  56.     const results = this.database.query(`SELECT * FROM users WHERE id = ${id}`);
  57.     return results[0];
  58.   }
  59.   
  60.   createUser(userData: any) {
  61.     this.logger.log(`Creating new user: ${JSON.stringify(userData)}`);
  62.     this.database.query(`INSERT INTO users (name, email) VALUES ('${userData.name}', '${userData.email}')`);
  63.     return { ...userData, id: Math.floor(Math.random() * 1000) };
  64.   }
  65. }
  66. const userService = new UserService();
  67. const user = userService.getUser(1);
  68. console.log("Retrieved user:", user);
  69. const newUser = userService.createUser({ name: "John Doe", email: "john@example.com" });
  70. console.log("Created user:", newUser);
复制代码

性能监控

装饰器可以用于监控方法或函数的性能,帮助开发者识别性能瓶颈。
  1. // 性能监控装饰器
  2. function performanceMonitor(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  3.   const originalMethod = descriptor.value;
  4.   
  5.   descriptor.value = function(...args: any[]) {
  6.     const start = performance.now();
  7.     const result = originalMethod.apply(this, args);
  8.     const end = performance.now();
  9.     const duration = end - start;
  10.    
  11.     console.log(`[PERFORMANCE] ${propertyKey} executed in ${duration.toFixed(2)}ms`);
  12.    
  13.     // 如果执行时间超过阈值,记录警告
  14.     if (duration > 100) {
  15.       console.warn(`[PERFORMANCE WARNING] ${propertyKey} is slow (${duration.toFixed(2)}ms)`);
  16.     }
  17.    
  18.     return result;
  19.   };
  20.   
  21.   return descriptor;
  22. }
  23. // 性能监控类装饰器
  24. function monitorClassPerformance<T extends { new(...args: any[]): {} }>(constructor: T) {
  25.   return class extends constructor {
  26.     // 为所有方法添加性能监控
  27.     [key: string]: any;
  28.    
  29.     constructor(...args: any[]) {
  30.       super(...args);
  31.       
  32.       // 获取所有方法(包括继承的方法)
  33.       const methods = Object.getOwnPropertyNames(Object.getPrototypeOf(this))
  34.         .filter(name => {
  35.           const descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this), name);
  36.           return typeof descriptor?.value === 'function' && name !== 'constructor';
  37.         });
  38.       
  39.       // 为每个方法添加性能监控
  40.       methods.forEach(methodName => {
  41.         const originalMethod = (this as any)[methodName];
  42.         (this as any)[methodName] = function(...args: any[]) {
  43.           const start = performance.now();
  44.           const result = originalMethod.apply(this, args);
  45.           const end = performance.now();
  46.           const duration = end - start;
  47.          
  48.           console.log(`[PERFORMANCE] ${methodName} executed in ${duration.toFixed(2)}ms`);
  49.          
  50.           if (duration > 100) {
  51.             console.warn(`[PERFORMANCE WARNING] ${methodName} is slow (${duration.toFixed(2)}ms)`);
  52.           }
  53.          
  54.           return result;
  55.         };
  56.       });
  57.     }
  58.   };
  59. }
  60. class DataProcessor {
  61.   @performanceMonitor
  62.   processSmallData() {
  63.     // 模拟快速处理
  64.     let sum = 0;
  65.     for (let i = 0; i < 1000; i++) {
  66.       sum += i;
  67.     }
  68.     return sum;
  69.   }
  70.   
  71.   @performanceMonitor
  72.   processLargeData() {
  73.     // 模拟慢速处理
  74.     let sum = 0;
  75.     for (let i = 0; i < 10000000; i++) {
  76.       sum += i;
  77.     }
  78.     return sum;
  79.   }
  80. }
  81. @monitorClassPerformance
  82. class AnotherDataProcessor {
  83.   quickOperation() {
  84.     return "Done quickly";
  85.   }
  86.   
  87.   slowOperation() {
  88.     // 模拟慢速操作
  89.     const start = Date.now();
  90.     while (Date.now() - start < 200) {
  91.       // 等待200ms
  92.     }
  93.     return "Done slowly";
  94.   }
  95. }
  96. const processor = new DataProcessor();
  97. processor.processSmallData();
  98. processor.processLargeData();
  99. const anotherProcessor = new AnotherDataProcessor();
  100. anotherProcessor.quickOperation();
  101. anotherProcessor.slowOperation();
复制代码

缓存机制

装饰器可以用于实现方法结果的缓存,避免重复计算或查询,提高性能。
  1. // 简单的内存缓存
  2. class SimpleCache {
  3.   private cache: Map<string, { value: any; expiry: number }> = new Map();
  4.   
  5.   get(key: string): any | null {
  6.     const item = this.cache.get(key);
  7.     if (!item) return null;
  8.    
  9.     if (Date.now() > item.expiry) {
  10.       this.cache.delete(key);
  11.       return null;
  12.     }
  13.    
  14.     return item.value;
  15.   }
  16.   
  17.   set(key: string, value: any, ttlMs: number = 5000): void {
  18.     this.cache.set(key, {
  19.       value,
  20.       expiry: Date.now() + ttlMs
  21.     });
  22.   }
  23.   
  24.   clear(): void {
  25.     this.cache.clear();
  26.   }
  27. }
  28. const cache = new SimpleCache();
  29. // 缓存装饰器工厂
  30. function cacheResult(ttlMs: number = 5000) {
  31.   return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  32.     const originalMethod = descriptor.value;
  33.    
  34.     descriptor.value = function(...args: any[]) {
  35.       // 生成缓存键
  36.       const cacheKey = `${propertyKey}:${JSON.stringify(args)}`;
  37.       
  38.       // 尝试从缓存获取结果
  39.       const cachedResult = cache.get(cacheKey);
  40.       if (cachedResult !== null) {
  41.         console.log(`[CACHE] Hit for ${propertyKey} with args: ${JSON.stringify(args)}`);
  42.         return cachedResult;
  43.       }
  44.       
  45.       // 缓存未命中,执行方法
  46.       console.log(`[CACHE] Miss for ${propertyKey} with args: ${JSON.stringify(args)}`);
  47.       const result = originalMethod.apply(this, args);
  48.       
  49.       // 存入缓存
  50.       cache.set(cacheKey, result, ttlMs);
  51.       
  52.       return result;
  53.     };
  54.    
  55.     return descriptor;
  56.   };
  57. }
  58. class FibonacciService {
  59.   @cacheResult(10000) // 缓存10秒
  60.   fibonacci(n: number): number {
  61.     console.log(`Computing fibonacci(${n})...`);
  62.     if (n <= 1) return n;
  63.     return this.fibonacci(n - 1) + this.fibonacci(n - 2);
  64.   }
  65.   
  66.   @cacheResult()
  67.   getUserData(userId: number) {
  68.     console.log(`Fetching user data for ID: ${userId}`);
  69.     // 模拟API调用
  70.     return {
  71.       id: userId,
  72.       name: `User ${userId}`,
  73.       email: `user${userId}@example.com`,
  74.       timestamp: Date.now()
  75.     };
  76.   }
  77. }
  78. const fibonacciService = new FibonacciService();
  79. // 第一次调用会计算
  80. console.log("Result:", fibonacciService.fibonacci(10));
  81. // 第二次调用会从缓存获取
  82. console.log("Result:", fibonacciService.fibonacci(10));
  83. // 第一次调用用户数据
  84. console.log("User:", fibonacciService.getUserData(123));
  85. // 第二次调用用户数据(从缓存获取)
  86. console.log("User:", fibonacciService.getUserData(123));
  87. // 等待一段时间后再次调用
  88. setTimeout(() => {
  89.   console.log("After timeout, User:", fibonacciService.getUserData(123));
  90. }, 6000);
复制代码

实战技巧与最佳实践

在使用TypeScript装饰器时,有一些技巧和最佳实践可以帮助我们更好地利用这一特性。

装饰器工厂模式

装饰器工厂是一个返回装饰器函数的函数,它允许我们向装饰器传递参数,使装饰器更加灵活和可配置。
  1. // 装饰器工厂示例
  2. function logWithPrefix(prefix: string) {
  3.   // 返回实际的装饰器函数
  4.   return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  5.     const originalMethod = descriptor.value;
  6.    
  7.     descriptor.value = function(...args: any[]) {
  8.       console.log(`[${prefix}] Starting ${propertyKey}`);
  9.       const result = originalMethod.apply(this, args);
  10.       console.log(`[${prefix}] Finished ${propertyKey}`);
  11.       return result;
  12.     };
  13.    
  14.     return descriptor;
  15.   };
  16. }
  17. class ApiService {
  18.   @logWithPrefix("API")
  19.   fetchData(endpoint: string) {
  20.     console.log(`Fetching data from ${endpoint}`);
  21.     return { data: `Data from ${endpoint}` };
  22.   }
  23.   
  24.   @logWithPrefix("AUTH")
  25.   authenticate(username: string, password: string) {
  26.     console.log(`Authenticating user ${username}`);
  27.     return { success: true, token: "fake-jwt-token" };
  28.   }
  29. }
  30. const apiService = new ApiService();
  31. apiService.fetchData("/users");
  32. apiService.authenticate("admin", "password");
复制代码

装饰器组合

TypeScript允许多个装饰器应用于同一个声明,它们会按照从下到上的顺序组合应用。
  1. function first() {
  2.   console.log("first(): factory evaluated");
  3.   return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  4.     console.log("first(): called");
  5.   };
  6. }
  7. function second() {
  8.   console.log("second(): factory evaluated");
  9.   return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  10.     console.log("second(): called");
  11.   };
  12. }
  13. function third() {
  14.   console.log("third(): factory evaluated");
  15.   return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  16.     console.log("third(): called");
  17.   };
  18. }
  19. class ExampleClass {
  20.   @first()
  21.   @second()
  22.   @third()
  23.   method() {}
  24. }
  25. // 输出顺序:
  26. // first(): factory evaluated
  27. // second(): factory evaluated
  28. // third(): factory evaluated
  29. // third(): called
  30. // second(): called
  31. // first(): called
复制代码

装饰器元数据

TypeScript支持通过reflect-metadata库为装饰器添加元数据,这对于实现依赖注入等功能特别有用。

首先,安装reflect-metadata:
  1. npm install reflect-metadata
复制代码

然后,在应用入口导入它:
  1. import "reflect-metadata";
复制代码

使用元数据的示例:
  1. import "reflect-metadata";
  2. // 定义元数据键
  3. const DESIGN_PARAM_TYPES = "design:paramtypes";
  4. const DESIGN_RETURN_TYPE = "design:returntype";
  5. const DESIGN_TYPE = "design:type";
  6. // 类型装饰器
  7. function typeInfo(target: any, propertyKey: string) {
  8.   const types = Reflect.getMetadata(DESIGN_TYPE, target, propertyKey);
  9.   console.log(`Type of ${propertyKey}: ${types?.name}`);
  10.   
  11.   const paramTypes = Reflect.getMetadata(DESIGN_PARAM_TYPES, target, propertyKey);
  12.   if (paramTypes) {
  13.     console.log(`Parameter types for ${propertyKey}:`, paramTypes.map(t => t.name));
  14.   }
  15.   
  16.   const returnType = Reflect.getMetadata(DESIGN_RETURN_TYPE, target, propertyKey);
  17.   if (returnType) {
  18.     console.log(`Return type for ${propertyKey}: ${returnType.name}`);
  19.   }
  20. }
  21. class UserService {
  22.   @typeInfo
  23.   name: string;
  24.   
  25.   @typeInfo
  26.   getUser(id: number): string {
  27.     return `User ${id}`;
  28.   }
  29.   
  30.   @typeInfo
  31.   createUser(name: string, email: string): { id: number; name: string; email: string } {
  32.     return { id: Date.now(), name, email };
  33.   }
  34. }
  35. // 输出:
  36. // Type of name: String
  37. // Type of getUser: Function
  38. // Parameter types for getUser: [ 'Number' ]
  39. // Return type for getUser: String
  40. // Type of createUser: Function
  41. // Parameter types for createUser: [ 'String', 'String' ]
  42. // Return type for createUser: Object
复制代码

装饰器与反射结合

结合反射API,我们可以创建更强大的装饰器,例如自动序列化和反序列化对象。
  1. import "reflect-metadata";
  2. // 序列化元数据键
  3. const SERIALIZE_KEY = "custom:serialize";
  4. // 序列化装饰器
  5. function serialize() {
  6.   return function(target: any, propertyKey: string) {
  7.     Reflect.defineMetadata(SERIALIZE_KEY, true, target, propertyKey);
  8.   };
  9. }
  10. // 序列化工具
  11. class SerializationHelper {
  12.   static serialize(obj: any): any {
  13.     const result: any = {};
  14.    
  15.     // 获取对象的所有属性
  16.     for (const key in obj) {
  17.       if (obj.hasOwnProperty(key)) {
  18.         // 检查属性是否有序列化元数据
  19.         const shouldSerialize = Reflect.getMetadata(SERIALIZE_KEY, obj, key);
  20.         
  21.         if (shouldSerialize) {
  22.           result[key] = obj[key];
  23.         }
  24.       }
  25.     }
  26.    
  27.     return result;
  28.   }
  29.   
  30.   static deserialize<T>(constructor: new (...args: any[]) => T, data: any): T {
  31.     const instance = new constructor();
  32.    
  33.     // 获取构造函数的所有属性
  34.     const properties = Object.getOwnPropertyNames(constructor.prototype);
  35.    
  36.     for (const key of properties) {
  37.       if (key !== "constructor" && data[key] !== undefined) {
  38.         // 检查属性是否有序列化元数据
  39.         const shouldSerialize = Reflect.getMetadata(SERIALIZE_KEY, constructor.prototype, key);
  40.         
  41.         if (shouldSerialize) {
  42.           instance[key] = data[key];
  43.         }
  44.       }
  45.     }
  46.    
  47.     return instance;
  48.   }
  49. }
  50. class User {
  51.   @serialize()
  52.   id: number;
  53.   
  54.   @serialize()
  55.   name: string;
  56.   
  57.   @serialize()
  58.   email: string;
  59.   
  60.   // 这个属性不会被序列化
  61.   password: string;
  62.   
  63.   constructor(id?: number, name?: string, email?: string, password?: string) {
  64.     this.id = id ?? 0;
  65.     this.name = name ?? "";
  66.     this.email = email ?? "";
  67.     this.password = password ?? "";
  68.   }
  69. }
  70. // 创建用户
  71. const user = new User(1, "John Doe", "john@example.com", "secret");
  72. // 序列化用户
  73. const serialized = SerializationHelper.serialize(user);
  74. console.log("Serialized user:", serialized);
  75. // 输出: Serialized user: { id: 1, name: 'John Doe', email: 'john@example.com' }
  76. // 反序列化用户
  77. const deserialized = SerializationHelper.deserialize(User, { id: 2, name: "Jane Smith", email: "jane@example.com" });
  78. console.log("Deserialized user:", deserialized);
  79. // 输出: Deserialized user: User { id: 2, name: 'Jane Smith', email: 'jane@example.com', password: '' }
复制代码

装饰器错误处理

在装饰器中正确处理错误非常重要,特别是在生产环境中。
  1. function safeDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  2.   const originalMethod = descriptor.value;
  3.   
  4.   descriptor.value = function(...args: any[]) {
  5.     try {
  6.       // 记录方法调用
  7.       console.log(`[SAFE] Calling ${propertyKey} with args:`, args);
  8.       
  9.       // 执行原始方法
  10.       const result = originalMethod.apply(this, args);
  11.       
  12.       // 处理Promise
  13.       if (result && typeof result.then === 'function') {
  14.         return result
  15.           .then((res: any) => {
  16.             console.log(`[SAFE] ${propertyKey} resolved successfully`);
  17.             return res;
  18.           })
  19.           .catch((err: any) => {
  20.             console.error(`[SAFE] ${propertyKey} rejected with error:`, err);
  21.             // 可以在这里添加错误恢复逻辑或重新抛出错误
  22.             throw err;
  23.           });
  24.       }
  25.       
  26.       console.log(`[SAFE] ${propertyKey} executed successfully`);
  27.       return result;
  28.     } catch (error) {
  29.       console.error(`[SAFE] Error in ${propertyKey}:`, error);
  30.       // 可以在这里添加错误恢复逻辑或重新抛出错误
  31.       throw error;
  32.     }
  33.   };
  34.   
  35.   return descriptor;
  36. }
  37. class ServiceWithErrors {
  38.   @safeDecorator
  39.   synchronousMethod() {
  40.     console.log("Executing synchronous method");
  41.     if (Math.random() > 0.5) {
  42.       throw new Error("Random synchronous error");
  43.     }
  44.     return "Sync result";
  45.   }
  46.   
  47.   @safeDecorator
  48.   asynchronousMethod() {
  49.     console.log("Executing asynchronous method");
  50.     return new Promise((resolve, reject) => {
  51.       setTimeout(() => {
  52.         if (Math.random() > 0.5) {
  53.           reject(new Error("Random asynchronous error"));
  54.         } else {
  55.           resolve("Async result");
  56.         }
  57.       }, 100);
  58.     });
  59.   }
  60. }
  61. const service = new ServiceWithErrors();
  62. // 测试同步方法
  63. for (let i = 0; i < 3; i++) {
  64.   try {
  65.     console.log(`Sync call ${i + 1}:`, service.synchronousMethod());
  66.   } catch (error) {
  67.     console.log(`Sync call ${i + 1} failed:`, error.message);
  68.   }
  69. }
  70. // 测试异步方法
  71. for (let i = 0; i < 3; i++) {
  72.   service.asynchronousMethod()
  73.     .then(result => console.log(`Async call ${i + 1}:`, result))
  74.     .catch(error => console.log(`Async call ${i + 1} failed:`, error.message));
  75. }
复制代码

装饰器与框架集成

许多流行的TypeScript框架和库都广泛使用装饰器,下面我们来看看如何在几个主流框架中使用装饰器。

Angular中的装饰器

Angular是一个广泛使用装饰器的前端框架,几乎所有核心功能都通过装饰器实现。
  1. import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
  2. // 组件装饰器
  3. @Component({
  4.   selector: 'app-user-profile',
  5.   templateUrl: './user-profile.component.html',
  6.   styleUrls: ['./user-profile.component.css']
  7. })
  8. export class UserProfileComponent implements OnInit {
  9.   // 输入属性装饰器
  10.   @Input() user: any;
  11.   
  12.   // 输出事件装饰器
  13.   @Output() updateUser = new EventEmitter<any>();
  14.   
  15.   constructor() { }
  16.   
  17.   // 生命周期钩子
  18.   ngOnInit(): void {
  19.     console.log('UserProfileComponent initialized');
  20.   }
  21.   
  22.   // 方法装饰器(自定义)
  23.   @log
  24.   saveUser() {
  25.     this.updateUser.emit(this.user);
  26.   }
  27. }
  28. // 自定义日志装饰器
  29. function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  30.   const originalMethod = descriptor.value;
  31.   
  32.   descriptor.value = function(...args: any[]) {
  33.     console.log(`[${target.constructor.name}] Calling ${propertyKey}`);
  34.     return originalMethod.apply(this, args);
  35.   };
  36.   
  37.   return descriptor;
  38. }
复制代码

NestJS中的装饰器

NestJS是一个用于构建高效、可扩展的Node.js服务器端应用程序的框架,它大量使用装饰器。
  1. import { Controller, Get, Post, Body, Param, UseGuards, SetMetadata } from '@nestjs/common';
  2. import { UserService } from './user.service';
  3. import { CreateUserDto } from './dto/create-user.dto';
  4. import { JwtAuthGuard } from '../auth/jwt-auth.guard';
  5. import { RolesGuard } from '../auth/roles.guard';
  6. import { Roles } from '../auth/roles.decorator';
  7. // 控制器装饰器
  8. @Controller('users')
  9. @UseGuards(JwtAuthGuard, RolesGuard)
  10. export class UsersController {
  11.   constructor(private readonly userService: UserService) {}
  12.   // 路由装饰器
  13.   @Get()
  14.   @Roles('admin')
  15.   findAll() {
  16.     return this.userService.findAll();
  17.   }
  18.   @Get(':id')
  19.   @Roles('admin', 'user')
  20.   findOne(@Param('id') id: string) {
  21.     return this.userService.findOne(id);
  22.   }
  23.   @Post()
  24.   @Roles('admin')
  25.   create(@Body() createUserDto: CreateUserDto) {
  26.     return this.userService.create(createUserDto);
  27.   }
  28. }
  29. // 自定义角色装饰器
  30. export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
  31. // 角色守卫
  32. import { CanActivate, ExecutionContext } from '@nestjs/common';
  33. import { Reflector } from '@nestjs/core';
  34. export class RolesGuard implements CanActivate {
  35.   constructor(private reflector: Reflector) {}
  36.   canActivate(context: ExecutionContext): boolean {
  37.     const roles = this.reflector.get<string[]>('roles', context.getHandler());
  38.     if (!roles) {
  39.       return true;
  40.     }
  41.    
  42.     const request = context.switchToHttp().getRequest();
  43.     const user = request.user;
  44.    
  45.     return roles.some(role => user.roles?.includes(role));
  46.   }
  47. }
复制代码

TypeORM中的装饰器

TypeORM是一个ORM框架,它使用装饰器来定义实体和关系。
  1. import { Entity, PrimaryGeneratedColumn, Column, OneToMany, ManyToOne, JoinColumn } from 'typeorm';
  2. import { Post } from './Post';
  3. import { Profile } from './Profile';
  4. // 实体装饰器
  5. @Entity()
  6. export class User {
  7.   // 主键装饰器
  8.   @PrimaryGeneratedColumn()
  9.   id: number;
  10.   // 列装饰器
  11.   @Column()
  12.   firstName: string;
  13.   @Column()
  14.   lastName: string;
  15.   @Column({ unique: true })
  16.   email: string;
  17.   @Column()
  18.   @Column({ select: false }) // 不默认查询此列
  19.   password: string;
  20.   @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
  21.   createdAt: Date;
  22.   // 一对多关系装饰器
  23.   @OneToMany(() => Post, post => post.author)
  24.   posts: Post[];
  25.   // 一对一关系装饰器
  26.   @ManyToOne(() => Profile, profile => profile.user)
  27.   @JoinColumn()
  28.   profile: Profile;
  29. }
  30. @Entity()
  31. export class Post {
  32.   @PrimaryGeneratedColumn()
  33.   id: number;
  34.   @Column()
  35.   title: string;
  36.   @Column('text')
  37.   content: string;
  38.   // 多对一关系装饰器
  39.   @ManyToOne(() => User, user => user.posts)
  40.   author: User;
  41. }
复制代码

class-validator中的装饰器

class-validator是一个用于对象验证的库,它使用装饰器来定义验证规则。
  1. import { validate, IsEmail, IsNotEmpty, Length, IsOptional, IsNumberString } from 'class-validator';
  2. export class CreateUserDto {
  3.   @IsNotEmpty()
  4.   @Length(3, 20)
  5.   username: string;
  6.   @IsEmail()
  7.   email: string;
  8.   @IsNotEmpty()
  9.   @Length(8, 100)
  10.   password: string;
  11.   @IsOptional()
  12.   @Length(0, 100)
  13.   bio?: string;
  14.   @IsOptional()
  15.   @IsNumberString()
  16.   age?: string;
  17. }
  18. // 使用验证
  19. async function createUser(userData: CreateUserDto) {
  20.   const user = new CreateUserDto();
  21.   user.username = userData.username;
  22.   user.email = userData.email;
  23.   user.password = userData.password;
  24.   user.bio = userData.bio;
  25.   user.age = userData.age;
  26.   const errors = await validate(user);
  27.   if (errors.length > 0) {
  28.     console.log('Validation failed. Errors:', errors);
  29.     throw new Error('Validation failed');
  30.   } else {
  31.     console.log('Validation succeeded');
  32.     // 保存用户到数据库
  33.     return { success: true, user };
  34.   }
  35. }
  36. // 测试验证
  37. createUser({
  38.   username: 'john_doe',
  39.   email: 'john@example.com',
  40.   password: 'password123'
  41. }).then(console.log).catch(console.error);
  42. createUser({
  43.   username: 'jd', // 太短
  44.   email: 'invalid-email', // 无效邮箱
  45.   password: '123', // 太短
  46.   age: 'not-a-number' // 不是数字字符串
  47. }).then(console.log).catch(console.error);
复制代码

提升代码质量与开发效率的具体案例

让我们通过几个具体的案例,看看装饰器如何在实际项目中提升代码质量和开发效率。

案例1:API请求处理

在处理API请求时,我们通常需要处理错误、验证数据、转换响应等。使用装饰器可以将这些横切关注点与业务逻辑分离。
  1. import "reflect-metadata";
  2. // 定义错误类型
  3. class ApiError extends Error {
  4.   constructor(
  5.     public statusCode: number,
  6.     message: string
  7.   ) {
  8.     super(message);
  9.     this.name = "ApiError";
  10.   }
  11. }
  12. // 路由处理装饰器
  13. function routeHandler() {
  14.   return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  15.     const originalMethod = descriptor.value;
  16.    
  17.     descriptor.value = async function(req: any, res: any, next: any) {
  18.       try {
  19.         // 执行原始方法
  20.         const result = await originalMethod.apply(this, [req, res, next]);
  21.         
  22.         // 如果方法返回结果,则发送JSON响应
  23.         if (result !== undefined) {
  24.           res.json({
  25.             success: true,
  26.             data: result
  27.           });
  28.         }
  29.       } catch (error) {
  30.         // 处理已知错误
  31.         if (error instanceof ApiError) {
  32.           res.status(error.statusCode).json({
  33.             success: false,
  34.             message: error.message
  35.           });
  36.           return;
  37.         }
  38.         
  39.         // 处理未知错误
  40.         console.error("Unhandled error:", error);
  41.         res.status(500).json({
  42.           success: false,
  43.           message: "Internal server error"
  44.         });
  45.       }
  46.     };
  47.    
  48.     return descriptor;
  49.   };
  50. }
  51. // 验证请求体装饰器
  52. function validateBody(dtoClass: any) {
  53.   return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  54.     const originalMethod = descriptor.value;
  55.    
  56.     descriptor.value = async function(req: any, res: any, next: any) {
  57.       try {
  58.         // 创建DTO实例
  59.         const dto = Object.assign(new dtoClass(), req.body);
  60.         
  61.         // 验证DTO
  62.         const errors = await validate(dto);
  63.         if (errors.length > 0) {
  64.           throw new ApiError(400, "Invalid request body");
  65.         }
  66.         
  67.         // 将验证后的DTO附加到请求对象
  68.         req.validatedBody = dto;
  69.         
  70.         // 调用原始方法
  71.         return originalMethod.apply(this, [req, res, next]);
  72.       } catch (error) {
  73.         next(error);
  74.       }
  75.     };
  76.    
  77.     return descriptor;
  78.   };
  79. }
  80. // 示例DTO
  81. import { validate, IsEmail, IsNotEmpty, Length } from 'class-validator';
  82. class CreateUserDto {
  83.   @IsNotEmpty()
  84.   @Length(3, 20)
  85.   username: string;
  86.   @IsEmail()
  87.   email: string;
  88.   @IsNotEmpty()
  89.   @Length(8, 100)
  90.   password: string;
  91. }
  92. // 用户控制器
  93. class UserController {
  94.   @routeHandler()
  95.   @validateBody(CreateUserDto)
  96.   async createUser(req: any, res: any) {
  97.     const { username, email, password } = req.validatedBody;
  98.    
  99.     // 检查用户是否已存在
  100.     const existingUser = await findUserByEmail(email);
  101.     if (existingUser) {
  102.       throw new ApiError(409, "User with this email already exists");
  103.     }
  104.    
  105.     // 创建用户
  106.     const user = await saveUser({ username, email, password });
  107.    
  108.     // 返回用户数据(不包含密码)
  109.     const { password: _, ...userWithoutPassword } = user;
  110.     return userWithoutPassword;
  111.   }
  112.   
  113.   @routeHandler()
  114.   async getUser(req: any, res: any) {
  115.     const userId = parseInt(req.params.id);
  116.    
  117.     if (isNaN(userId)) {
  118.       throw new ApiError(400, "Invalid user ID");
  119.     }
  120.    
  121.     const user = await findUserById(userId);
  122.     if (!user) {
  123.       throw new ApiError(404, "User not found");
  124.     }
  125.    
  126.     const { password: _, ...userWithoutPassword } = user;
  127.     return userWithoutPassword;
  128.   }
  129. }
  130. // 模拟数据库函数
  131. async function findUserByEmail(email: string) {
  132.   // 模拟数据库查询
  133.   return null;
  134. }
  135. async function findUserById(id: number) {
  136.   // 模拟数据库查询
  137.   if (id === 1) {
  138.     return {
  139.       id: 1,
  140.       username: "john_doe",
  141.       email: "john@example.com",
  142.       password: "hashed_password"
  143.     };
  144.   }
  145.   return null;
  146. }
  147. async function saveUser(userData: any) {
  148.   // 模拟保存用户
  149.   return {
  150.     id: Math.floor(Math.random() * 1000),
  151.     ...userData
  152.   };
  153. }
  154. // 模拟Express请求处理
  155. async function simulateRequest(controller: any, method: string, req: any) {
  156.   const res = {
  157.     json: (data: any) => console.log("Response:", JSON.stringify(data, null, 2)),
  158.     status: (code: number) => {
  159.       console.log(`Status: ${code}`);
  160.       return res;
  161.     }
  162.   };
  163.   
  164.   const next = (error: any) => {
  165.     console.log("Error:", error.message);
  166.   };
  167.   
  168.   try {
  169.     await controller[method](req, res, next);
  170.   } catch (error) {
  171.     next(error);
  172.   }
  173. }
  174. // 测试用例
  175. const userController = new UserController();
  176. // 测试创建用户 - 成功
  177. console.log("\n=== Test 1: Create user (success) ===");
  178. simulateRequest(userController, "createUser", {
  179.   body: {
  180.     username: "john_doe",
  181.     email: "john@example.com",
  182.     password: "password123"
  183.   }
  184. });
  185. // 测试创建用户 - 验证失败
  186. console.log("\n=== Test 2: Create user (validation failure) ===");
  187. simulateRequest(userController, "createUser", {
  188.   body: {
  189.     username: "jd", // 太短
  190.     email: "invalid-email", // 无效邮箱
  191.     password: "123" // 太短
  192.   }
  193. });
  194. // 测试获取用户 - 成功
  195. console.log("\n=== Test 3: Get user (success) ===");
  196. simulateRequest(userController, "getUser", {
  197.   params: { id: "1" }
  198. });
  199. // 测试获取用户 - 无效ID
  200. console.log("\n=== Test 4: Get user (invalid ID) ===");
  201. simulateRequest(userController, "getUser", {
  202.   params: { id: "invalid" }
  203. });
  204. // 测试获取用户 - 不存在
  205. console.log("\n=== Test 5: Get user (not found) ===");
  206. simulateRequest(userController, "getUser", {
  207.   params: { id: "999" }
  208. });
复制代码

案例2:数据库事务管理

在处理数据库操作时,事务管理是一个重要的横切关注点。使用装饰器可以优雅地实现事务管理。
  1. import "reflect-metadata";
  2. // 事务元数据键
  3. const TRANSACTION_KEY = "custom:transaction";
  4. // 事务装饰器
  5. function transactional() {
  6.   return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  7.     const originalMethod = descriptor.value;
  8.    
  9.     descriptor.value = async function(...args: any[]) {
  10.       // 开始事务
  11.       const transaction = await this.beginTransaction();
  12.       console.log(`[TRANSACTION] Started for ${propertyKey}`);
  13.       
  14.       try {
  15.         // 将事务附加到当前实例,以便其他方法使用
  16.         Reflect.defineMetadata(TRANSACTION_KEY, transaction, this);
  17.         
  18.         // 执行原始方法
  19.         const result = await originalMethod.apply(this, args);
  20.         
  21.         // 提交事务
  22.         await this.commitTransaction(transaction);
  23.         console.log(`[TRANSACTION] Committed for ${propertyKey}`);
  24.         
  25.         return result;
  26.       } catch (error) {
  27.         // 回滚事务
  28.         await this.rollbackTransaction(transaction);
  29.         console.log(`[TRANSACTION] Rolled back for ${propertyKey}`);
  30.         
  31.         // 重新抛出错误
  32.         throw error;
  33.       } finally {
  34.         // 清除事务元数据
  35.         Reflect.deleteMetadata(TRANSACTION_KEY, this);
  36.       }
  37.     };
  38.    
  39.     return descriptor;
  40.   };
  41. }
  42. // 获取当前事务的辅助函数
  43. function getCurrentTransaction(target: any) {
  44.   return Reflect.getMetadata(TRANSACTION_KEY, target);
  45. }
  46. // 模拟数据库服务
  47. class DatabaseService {
  48.   private transactions: Map<string, any> = new Map();
  49.   
  50.   async beginTransaction() {
  51.     const id = `tx_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  52.     const transaction = { id, committed: false, rolledBack: false };
  53.     this.transactions.set(id, transaction);
  54.     console.log(`[DB] Begin transaction: ${id}`);
  55.     return transaction;
  56.   }
  57.   
  58.   async commitTransaction(transaction: any) {
  59.     if (transaction.rolledBack) {
  60.       throw new Error("Cannot commit a rolled back transaction");
  61.     }
  62.    
  63.     transaction.committed = true;
  64.     console.log(`[DB] Commit transaction: ${transaction.id}`);
  65.   }
  66.   
  67.   async rollbackTransaction(transaction: any) {
  68.     if (transaction.committed) {
  69.       throw new Error("Cannot rollback a committed transaction");
  70.     }
  71.    
  72.     transaction.rolledBack = true;
  73.     console.log(`[DB] Rollback transaction: ${transaction.id}`);
  74.   }
  75.   
  76.   async query(sql: string, params: any[] = [], transaction?: any) {
  77.     const txId = transaction ? ` (tx: ${transaction.id})` : "";
  78.     console.log(`[DB] Executing query${txId}: ${sql}`, params);
  79.    
  80.     // 模拟查询延迟
  81.     await new Promise(resolve => setTimeout(resolve, 100));
  82.    
  83.     // 模拟查询结果
  84.     if (sql.startsWith("INSERT")) {
  85.       return { insertId: Math.floor(Math.random() * 1000) };
  86.     } else if (sql.startsWith("SELECT")) {
  87.       return [
  88.         { id: 1, name: "John Doe", email: "john@example.com" },
  89.         { id: 2, name: "Jane Smith", email: "jane@example.com" }
  90.       ];
  91.     }
  92.    
  93.     return { affectedRows: 1 };
  94.   }
  95. }
  96. // 用户服务
  97. class UserService {
  98.   private db: DatabaseService;
  99.   
  100.   constructor(db: DatabaseService) {
  101.     this.db = db;
  102.   }
  103.   
  104.   async beginTransaction() {
  105.     return this.db.beginTransaction();
  106.   }
  107.   
  108.   async commitTransaction(transaction: any) {
  109.     return this.db.commitTransaction(transaction);
  110.   }
  111.   
  112.   async rollbackTransaction(transaction: any) {
  113.     return this.db.rollbackTransaction(transaction);
  114.   }
  115.   
  116.   @transactional()
  117.   async createUserWithProfile(userData: any, profileData: any) {
  118.     // 获取当前事务
  119.     const transaction = getCurrentTransaction(this);
  120.    
  121.     // 创建用户
  122.     const userResult = await this.db.query(
  123.       "INSERT INTO users (name, email) VALUES (?, ?)",
  124.       [userData.name, userData.email],
  125.       transaction
  126.     );
  127.    
  128.     const userId = userResult.insertId;
  129.    
  130.     // 创建用户档案
  131.     await this.db.query(
  132.       "INSERT INTO profiles (user_id, bio, avatar) VALUES (?, ?, ?)",
  133.       [userId, profileData.bio, profileData.avatar],
  134.       transaction
  135.     );
  136.    
  137.     // 返回创建的用户和档案
  138.     return {
  139.       user: { id: userId, ...userData },
  140.       profile: { userId, ...profileData }
  141.     };
  142.   }
  143.   
  144.   @transactional()
  145.   async transferFunds(fromUserId: number, toUserId: number, amount: number) {
  146.     // 获取当前事务
  147.     const transaction = getCurrentTransaction(this);
  148.    
  149.     // 检查发送方账户余额
  150.     const balanceResult = await this.db.query(
  151.       "SELECT balance FROM accounts WHERE user_id = ?",
  152.       [fromUserId],
  153.       transaction
  154.     );
  155.    
  156.     if (balanceResult.length === 0) {
  157.       throw new Error("Sender account not found");
  158.     }
  159.    
  160.     const balance = balanceResult[0].balance;
  161.    
  162.     if (balance < amount) {
  163.       throw new Error("Insufficient funds");
  164.     }
  165.    
  166.     // 从发送方账户扣除金额
  167.     await this.db.query(
  168.       "UPDATE accounts SET balance = balance - ? WHERE user_id = ?",
  169.       [amount, fromUserId],
  170.       transaction
  171.     );
  172.    
  173.     // 向接收方账户添加金额
  174.     await this.db.query(
  175.       "UPDATE accounts SET balance = balance + ? WHERE user_id = ?",
  176.       [amount, toUserId],
  177.       transaction
  178.     );
  179.    
  180.     // 记录交易
  181.     await this.db.query(
  182.       "INSERT INTO transactions (from_user_id, to_user_id, amount) VALUES (?, ?, ?)",
  183.       [fromUserId, toUserId, amount],
  184.       transaction
  185.     );
  186.    
  187.     return { success: true, message: "Transfer completed" };
  188.   }
  189. }
  190. // 测试
  191. const db = new DatabaseService();
  192. const userService = new UserService(db);
  193. // 测试创建用户和档案
  194. console.log("\n=== Test 1: Create user with profile ===");
  195. userService.createUserWithProfile(
  196.   { name: "John Doe", email: "john@example.com" },
  197.   { bio: "Software developer", avatar: "avatar.jpg" }
  198. ).then(result => {
  199.   console.log("Result:", result);
  200. }).catch(error => {
  201.   console.error("Error:", error.message);
  202. });
  203. // 测试资金转账 - 成功
  204. console.log("\n=== Test 2: Transfer funds (success) ===");
  205. userService.transferFunds(1, 2, 100)
  206.   .then(result => {
  207.     console.log("Result:", result);
  208.   })
  209.   .catch(error => {
  210.     console.error("Error:", error.message);
  211.   });
  212. // 测试资金转账 - 余额不足(应该回滚)
  213. console.log("\n=== Test 3: Transfer funds (insufficient funds) ===");
  214. userService.transferFunds(1, 2, 10000)
  215.   .then(result => {
  216.     console.log("Result:", result);
  217.   })
  218.   .catch(error => {
  219.     console.error("Error:", error.message);
  220.   });
复制代码

案例3:性能监控与优化

装饰器可以用于监控应用性能,帮助识别和解决性能瓶颈。
  1. // 性能监控装饰器
  2. function performanceMonitor(thresholdMs: number = 100) {
  3.   return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  4.     const originalMethod = descriptor.value;
  5.    
  6.     descriptor.value = async function(...args: any[]) {
  7.       const startTime = performance.now();
  8.       const memoryBefore = process.memoryUsage();
  9.       
  10.       try {
  11.         // 执行原始方法
  12.         const result = await originalMethod.apply(this, args);
  13.         
  14.         const endTime = performance.now();
  15.         const memoryAfter = process.memoryUsage();
  16.         const duration = endTime - startTime;
  17.         
  18.         // 计算内存使用差异
  19.         const memoryDiff = {
  20.           rss: memoryAfter.rss - memoryBefore.rss,
  21.           heapTotal: memoryAfter.heapTotal - memoryBefore.heapTotal,
  22.           heapUsed: memoryAfter.heapUsed - memoryBefore.heapUsed,
  23.           external: memoryAfter.external - memoryBefore.external
  24.         };
  25.         
  26.         // 记录性能数据
  27.         console.log(`[PERFORMANCE] ${propertyKey} completed in ${duration.toFixed(2)}ms`);
  28.         console.log(`[MEMORY] Usage difference:`, {
  29.           rss: `${(memoryDiff.rss / 1024 / 1024).toFixed(2)} MB`,
  30.           heapTotal: `${(memoryDiff.heapTotal / 1024 / 1024).toFixed(2)} MB`,
  31.           heapUsed: `${(memoryDiff.heapUsed / 1024 / 1024).toFixed(2)} MB`,
  32.           external: `${(memoryDiff.external / 1024 / 1024).toFixed(2)} MB`
  33.         });
  34.         
  35.         // 如果执行时间超过阈值,记录警告
  36.         if (duration > thresholdMs) {
  37.           console.warn(`[PERFORMANCE WARNING] ${propertyKey} exceeded threshold of ${thresholdMs}ms`);
  38.          
  39.           // 这里可以添加额外的性能分析逻辑
  40.           // 例如:记录调用堆栈、分析参数等
  41.         }
  42.         
  43.         return result;
  44.       } catch (error) {
  45.         const endTime = performance.now();
  46.         const duration = endTime - startTime;
  47.         
  48.         console.error(`[PERFORMANCE] ${propertyKey} failed after ${duration.toFixed(2)}ms`);
  49.         console.error(`[ERROR]`, error);
  50.         
  51.         throw error;
  52.       }
  53.     };
  54.    
  55.     return descriptor;
  56.   };
  57. }
  58. // 缓存装饰器
  59. function cache(ttlMs: number = 5000) {
  60.   const cacheStore = new Map<string, { value: any; expiry: number }>();
  61.   
  62.   return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  63.     const originalMethod = descriptor.value;
  64.    
  65.     descriptor.value = async function(...args: any[]) {
  66.       // 生成缓存键
  67.       const cacheKey = `${propertyKey}:${JSON.stringify(args)}`;
  68.       
  69.       // 检查缓存
  70.       const cachedItem = cacheStore.get(cacheKey);
  71.       if (cachedItem && Date.now() < cachedItem.expiry) {
  72.         console.log(`[CACHE] Hit for ${propertyKey}`);
  73.         return cachedItem.value;
  74.       }
  75.       
  76.       // 缓存未命中,执行方法
  77.       console.log(`[CACHE] Miss for ${propertyKey}`);
  78.       const result = await originalMethod.apply(this, args);
  79.       
  80.       // 存入缓存
  81.       cacheStore.set(cacheKey, {
  82.         value: result,
  83.         expiry: Date.now() + ttlMs
  84.       });
  85.       
  86.       return result;
  87.     };
  88.    
  89.     return descriptor;
  90.   };
  91. }
  92. // 数据服务
  93. class DataService {
  94.   @performanceMonitor(50)
  95.   @cache(10000) // 缓存10秒
  96.   async fetchData(id: number) {
  97.     console.log(`[SERVICE] Fetching data for ID: ${id}`);
  98.    
  99.     // 模拟网络请求
  100.     await new Promise(resolve => setTimeout(resolve, 30 + Math.random() * 50));
  101.    
  102.     // 模拟数据处理
  103.     let result = 0;
  104.     for (let i = 0; i < 100000; i++) {
  105.       result += Math.sqrt(i);
  106.     }
  107.    
  108.     return {
  109.       id,
  110.       data: `Sample data for ID ${id}`,
  111.       calculatedValue: result
  112.     };
  113.   }
  114.   
  115.   @performanceMonitor(200)
  116.   async processData(data: any[]) {
  117.     console.log(`[SERVICE] Processing ${data.length} items`);
  118.    
  119.     // 模拟复杂处理
  120.     await new Promise(resolve => setTimeout(resolve, 100 + Math.random() * 150));
  121.    
  122.     // 模拟CPU密集型操作
  123.     return data.map(item => {
  124.       let processed = { ...item };
  125.       
  126.       // 模拟一些计算
  127.       for (let i = 0; i < 10000; i++) {
  128.         processed.value = Math.sqrt(item.id * i);
  129.       }
  130.       
  131.       processed.processedAt = new Date().toISOString();
  132.       return processed;
  133.     });
  134.   }
  135. }
  136. // 测试
  137. const dataService = new DataService();
  138. // 测试数据获取(第一次会从服务获取,第二次会从缓存获取)
  139. console.log("\n=== Test 1: Fetch data (first call) ===");
  140. dataService.fetchData(123)
  141.   .then(result => {
  142.     console.log("Result:", result.id);
  143.   })
  144.   .catch(console.error);
  145. console.log("\n=== Test 2: Fetch data (second call - should be cached) ===");
  146. dataService.fetchData(123)
  147.   .then(result => {
  148.     console.log("Result:", result.id);
  149.   })
  150.   .catch(console.error);
  151. // 测试数据处理
  152. console.log("\n=== Test 3: Process data ===");
  153. const testData = Array.from({ length: 100 }, (_, i) => ({
  154.   id: i + 1,
  155.   name: `Item ${i + 1}`
  156. }));
  157. dataService.processData(testData)
  158.   .then(result => {
  159.     console.log(`Processed ${result.length} items`);
  160.   })
  161.   .catch(console.error);
复制代码

常见问题与解决方案

在使用TypeScript装饰器的过程中,开发者可能会遇到一些常见问题。下面我们探讨这些问题及其解决方案。

问题1:装饰器执行顺序

问题:多个装饰器应用于同一声明时,执行顺序可能不符合预期。

解决方案:理解装饰器的执行顺序。装饰器工厂函数从上到下执行,而装饰器函数从下到上执行。
  1. function decoratorA() {
  2.   console.log("decoratorA factory");
  3.   return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  4.     console.log("decoratorA called");
  5.   };
  6. }
  7. function decoratorB() {
  8.   console.log("decoratorB factory");
  9.   return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  10.     console.log("decoratorB called");
  11.   };
  12. }
  13. class Example {
  14.   @decoratorA()
  15.   @decoratorB()
  16.   method() {}
  17. }
  18. // 输出:
  19. // decoratorA factory
  20. // decoratorB factory
  21. // decoratorB called
  22. // decoratorA called
复制代码

问题2:装饰器与属性描述符

问题:属性装饰器不能修改属性描述符,导致无法直接添加getter/setter。

解决方案:使用Object.defineProperty在属性装饰器中重新定义属性。
  1. function logChanges(target: any, propertyKey: string) {
  2.   let value: any;
  3.   
  4.   const getter = function() {
  5.     console.log(`Getting value for ${propertyKey}`);
  6.     return value;
  7.   };
  8.   
  9.   const setter = function(newVal: any) {
  10.     console.log(`Setting value for ${propertyKey} to ${newVal}`);
  11.     value = newVal;
  12.   };
  13.   
  14.   Object.defineProperty(target, propertyKey, {
  15.     get: getter,
  16.     set: setter,
  17.     enumerable: true,
  18.     configurable: true
  19.   });
  20. }
  21. class MyClass {
  22.   @logChanges
  23.   myProperty: string;
  24. }
  25. const instance = new MyClass();
  26. instance.myProperty = "test";
  27. console.log(instance.myProperty);
复制代码

问题3:装饰器与继承

问题:装饰器在继承层次结构中的行为可能不符合预期,特别是当子类覆盖了父类的装饰方法时。

解决方案:了解装饰器如何影响继承链,并在必要时手动处理继承情况。
  1. function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  2.   const originalMethod = descriptor.value;
  3.   
  4.   descriptor.value = function(...args: any[]) {
  5.     console.log(`[${target.constructor.name}] Calling ${propertyKey}`);
  6.     return originalMethod.apply(this, args);
  7.   };
  8.   
  9.   return descriptor;
  10. }
  11. class Parent {
  12.   @log
  13.   method() {
  14.     console.log("Parent method implementation");
  15.   }
  16. }
  17. class Child extends Parent {
  18.   @log
  19.   method() {
  20.     super.method();
  21.     console.log("Child method implementation");
  22.   }
  23. }
  24. const child = new Child();
  25. child.method();
  26. // 输出:
  27. // [Child] Calling method
  28. // [Parent] Calling method
  29. // Parent method implementation
  30. // Child method implementation
复制代码

问题4:装饰器与私有属性

问题:装饰器无法直接访问类的私有属性或方法。

解决方案:使用TypeScript的元数据或反射API,或者将需要访问的成员改为protected/public。
  1. function accessPrivate(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  2.   const originalMethod = descriptor.value;
  3.   
  4.   descriptor.value = function(...args: any[]) {
  5.     // 无法直接访问私有属性
  6.     // console.log(this.privateProperty); // 错误
  7.    
  8.     // 使用反射API访问
  9.     const privateValue = (this as any)["privateProperty"];
  10.     console.log(`Accessed private property: ${privateValue}`);
  11.    
  12.     return originalMethod.apply(this, args);
  13.   };
  14.   
  15.   return descriptor;
  16. }
  17. class MyClass {
  18.   private privateProperty = "secret";
  19.   
  20.   @accessPrivate
  21.   method() {
  22.     console.log("Method called");
  23.   }
  24. }
  25. const instance = new MyClass();
  26. instance.method();
复制代码

问题5:装饰器与异步方法

问题:装饰器在处理异步方法时可能无法正确处理Promise。

解决方案:在装饰器中检查返回值是否为Promise,并相应地处理。
  1. function asyncLog(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
  2.   const originalMethod = descriptor.value;
  3.   
  4.   descriptor.value = async function(...args: any[]) {
  5.     console.log(`[${propertyKey}] Started`);
  6.    
  7.     try {
  8.       const result = originalMethod.apply(this, args);
  9.       
  10.       // 检查是否返回Promise
  11.       if (result && typeof result.then === 'function') {
  12.         const awaitedResult = await result;
  13.         console.log(`[${propertyKey}] Completed successfully`);
  14.         return awaitedResult;
  15.       }
  16.       
  17.       console.log(`[${propertyKey}] Completed successfully`);
  18.       return result;
  19.     } catch (error) {
  20.       console.error(`[${propertyKey}] Failed:`, error);
  21.       throw error;
  22.     }
  23.   };
  24.   
  25.   return descriptor;
  26. }
  27. class AsyncService {
  28.   @asyncLog
  29.   async asyncMethod() {
  30.     await new Promise(resolve => setTimeout(resolve, 100));
  31.     return "Async result";
  32.   }
  33.   
  34.   @asyncLog
  35.   syncMethod() {
  36.     return "Sync result";
  37.   }
  38. }
  39. const service = new AsyncService();
  40. service.asyncMethod().then(console.log);
  41. console.log(service.syncMethod());
复制代码

问题6:装饰器与严格模式

问题:在TypeScript严格模式下,装饰器可能会遇到类型检查问题。

解决方案:使用适当的类型注解和类型断言,确保装饰器函数的类型安全。
  1. // 使用适当的类型注解
  2. function typedDecorator(
  3.   target: Object,
  4.   propertyKey: string | symbol,
  5.   descriptor: TypedPropertyDescriptor<(...args: any[]) => any>
  6. ) {
  7.   const originalMethod = descriptor.value!;
  8.   
  9.   descriptor.value = function(this: any, ...args: any[]) {
  10.     console.log(`[${String(propertyKey)}] Called with args:`, args);
  11.     return originalMethod.apply(this, args);
  12.   };
  13.   
  14.   return descriptor;
  15. }
  16. class StrictClass {
  17.   @typedDecorator
  18.   method(arg1: string, arg2: number): boolean {
  19.     console.log(`Method called with ${arg1} and ${arg2}`);
  20.     return true;
  21.   }
  22. }
  23. const instance = new StrictClass();
  24. instance.method("test", 123);
复制代码

总结与展望

TypeScript装饰器是一种强大的元编程特性,它允许开发者以声明式的方式增强类、方法、属性和参数的功能。通过本文的深入探讨,我们了解了装饰器的基本概念、类型、实际应用场景以及实战技巧,并看到了装饰器如何在实际项目中提升代码质量和开发效率。

装饰器的优势

1. 关注点分离:装饰器可以将横切关注点(如日志、验证、缓存等)与业务逻辑分离,使代码更加清晰和可维护。
2. 代码复用:装饰器可以在多个地方重用,减少重复代码,提高开发效率。
3. 声明式编程:装饰器提供了一种声明式的方式来添加功能,使代码更加简洁和易读。
4. 元编程能力:装饰器提供了一种在运行时修改或扩展代码行为的能力,增加了代码的灵活性。

关注点分离:装饰器可以将横切关注点(如日志、验证、缓存等)与业务逻辑分离,使代码更加清晰和可维护。

代码复用:装饰器可以在多个地方重用,减少重复代码,提高开发效率。

声明式编程:装饰器提供了一种声明式的方式来添加功能,使代码更加简洁和易读。

元编程能力:装饰器提供了一种在运行时修改或扩展代码行为的能力,增加了代码的灵活性。

装饰器的局限性

1. 实验性特性:装饰器在TypeScript中仍然是一个实验性特性,其API和实现可能会在未来的版本中发生变化。
2. 性能开销:装饰器可能会引入一些性能开销,特别是在频繁调用的方法上。
3. 调试复杂性:由于装饰器在运行时修改代码行为,可能会增加调试的复杂性。
4. 学习曲线:对于初学者来说,装饰器的概念和工作原理可能需要一些时间来理解。

实验性特性:装饰器在TypeScript中仍然是一个实验性特性,其API和实现可能会在未来的版本中发生变化。

性能开销:装饰器可能会引入一些性能开销,特别是在频繁调用的方法上。

调试复杂性:由于装饰器在运行时修改代码行为,可能会增加调试的复杂性。

学习曲线:对于初学者来说,装饰器的概念和工作原理可能需要一些时间来理解。

未来展望

随着ECMAScript装饰器提案的推进,TypeScript装饰器的实现可能会进一步标准化和完善。我们可以期待:

1. 更稳定的API:随着装饰器提案的成熟,TypeScript可能会提供更稳定和一致的装饰器API。
2. 更好的工具支持:IDE和其他开发工具可能会提供更好的装饰器支持,包括代码提示、重构和调试功能。
3. 更广泛的应用:随着装饰器的普及,我们可能会看到更多的框架和库采用装饰器作为其核心特性。
4. 性能优化:未来的TypeScript版本可能会优化装饰器的实现,减少其性能开销。

更稳定的API:随着装饰器提案的成熟,TypeScript可能会提供更稳定和一致的装饰器API。

更好的工具支持:IDE和其他开发工具可能会提供更好的装饰器支持,包括代码提示、重构和调试功能。

更广泛的应用:随着装饰器的普及,我们可能会看到更多的框架和库采用装饰器作为其核心特性。

性能优化:未来的TypeScript版本可能会优化装饰器的实现,减少其性能开销。

最佳实践建议

1. 合理使用装饰器:不要过度使用装饰器,只在确实需要横切关注点时使用。
2. 保持装饰器简单:尽量保持装饰器函数简单和专注,避免在装饰器中实现复杂的逻辑。
3. 提供清晰的文档:为自定义装饰器提供清晰的文档,说明其用途、参数和行为。
4. 考虑性能影响:在性能敏感的代码中使用装饰器时,要考虑其性能影响,必要时进行性能测试。
5. 遵循命名约定:为装饰器函数和装饰器工厂使用一致的命名约定,提高代码可读性。

合理使用装饰器:不要过度使用装饰器,只在确实需要横切关注点时使用。

保持装饰器简单:尽量保持装饰器函数简单和专注,避免在装饰器中实现复杂的逻辑。

提供清晰的文档:为自定义装饰器提供清晰的文档,说明其用途、参数和行为。

考虑性能影响:在性能敏感的代码中使用装饰器时,要考虑其性能影响,必要时进行性能测试。

遵循命名约定:为装饰器函数和装饰器工厂使用一致的命名约定,提高代码可读性。

总之,TypeScript装饰器是一个强大的工具,可以帮助开发者编写更加清晰、可维护和高效的代码。通过合理地使用装饰器,我们可以显著提升代码质量和开发效率,使我们的应用程序更加健壮和易于维护。随着TypeScript和JavaScript生态系统的不断发展,装饰器无疑将在未来的前端和后端开发中扮演更加重要的角色。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

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

本版积分规则

关闭

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

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

Powered by Pixtech

© 2025-2026 Pixtech Team.

>