TypeScript装饰器应用实战
前言
装饰器是TypeScript中一个强大的元编程特性,它允许我们在不修改目标类或函数源代码的情况下,动态地扩展其功能。本文将深入探讨TypeScript装饰器的概念、语法、使用场景,并通过实际案例展示如何利用装饰器编写更加优雅、可维护的代码。
1. 装饰器基础
1.1 什么是装饰器
装饰器是一种特殊类型的声明,可以附加到类、方法、属性或参数上。它本质上是一个函数,在运行时被调用,接收被装饰的目标作为参数,并可以对其进行修改或增强。
1.2 启用装饰器支持
在TypeScript中,装饰器默认是关闭的。我们需要在tsconfig.json中启用:
{ "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } }
|
1.3 装饰器类型
TypeScript支持以下几种装饰器:
- 类装饰器 - 应用于类
- 方法装饰器 - 应用于方法
- 属性装饰器 - 应用于属性
- 参数装饰器 - 应用于参数
2. 类装饰器
2.1 基本语法
function sealed(constructor: Function) { Object.seal(constructor); Object.seal(constructor.prototype); }
@sealed class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet() { return "Hello, " + this.greeting; } }
|
2.2 实际应用:日志装饰器
function logClass(constructor: Function) { console.log(`Class ${constructor.name} created`); console.log('Class details:', constructor); }
@logClass class UserService { constructor(private username: string) {} getUser() { console.log(`Getting user: ${this.username}`); return { username: this.username }; } }
|
2.3 类装饰器工厂
function configurable(value: boolean) { return function (constructor: Function) { console.log(`Configuring class ${constructor.name} with value: ${value}`); }; }
@configurable(true) class Database { constructor(private connectionString: string) {} connect() { console.log(`Connecting to ${this.connectionString}`); } }
|
3. 方法装饰器
3.1 基本结构
function methodDecorator( target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor ) { console.log('Method decorator called'); descriptor.enumerable = false; }
class Calculator { @methodDecorator add(a: number, b: number): number { return a + b; } }
|
3.2 实际应用:性能监控
function measureTime() { return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function(...args: any[]) { const start = performance.now(); const result = originalMethod.apply(this, args); const end = performance.now(); console.log(`${propertyKey} 执行时间: ${end - start}ms`); return result; }; }; }
class DataProcessor { @measureTime() processLargeData(data: number[]): number[] { return data.map(item => item * 2); } }
|
3.3 权限控制装饰器
function requirePermission(permission: string) { return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function(...args: any[]) { if (!this.hasPermission(permission)) { throw new Error(`缺少权限: ${permission}`); } return originalMethod.apply(this, args); }; }; }
class UserController { private permissions = ['read', 'write']; hasPermission(permission: string): boolean { return this.permissions.includes(permission); } @requirePermission('admin') deleteAllUsers() { console.log('删除所有用户...'); } }
|
4. 属性装饰器
4.1 基本用法
function logProperty(target: Object, propertyKey: string) { console.log(`Property ${propertyKey} defined`); }
class User { @logProperty username: string; @logProperty email: string; constructor(username: string, email: string) { this.username = username; this.email = email; } }
|
4.2 实际应用:数据验证
function validateEmail(target: Object, propertyKey: string) { let value = target[propertyKey]; const getter = function() { return value; }; const setter = function(newVal: string) { if (!newVal.includes('@')) { throw new Error('无效的邮箱地址'); } value = newVal; }; Object.defineProperty(target, propertyKey, { get: getter, set: setter, enumerable: true, configurable: true }); }
class UserProfile { @validateEmail email: string; constructor(email: string) { this.email = email; } }
const profile = new UserProfile('user@example.com');
|
4.3 只读属性装饰器
function readonly(target: Object, propertyKey: string) { let value = target[propertyKey]; Object.defineProperty(target, propertyKey, { get: function() { return value; }, set: function() { throw new Error(`${propertyKey} 是只读的`); }, enumerable: true, configurable: false }); }
class Config { @readonly API_KEY = '12345-67890'; }
const config = new Config(); console.log(config.API_KEY);
|
5. 参数装饰器
5.1 基本用法
function logParameter(target: Object, propertyKey: string | symbol, parameterIndex: number) { console.log(`Parameter ${parameterIndex} for ${String(propertyKey)}`); }
class Logger { log(level: string, message: string, @logParameter timestamp: Date) { console.log(`[${level}] ${message} - ${timestamp}`); } }
|
5.2 实际应用:参数验证
function validateMinLength(min: number) { return function (target: Object, propertyKey: string | symbol, parameterIndex: number) { const originalMethod = target[propertyKey]; target[propertyKey] = function(...args: any[]) { const paramValue = args[parameterIndex]; if (paramValue.length < min) { throw new Error(`参数长度必须大于等于 ${min}`); } return originalMethod.apply(this, args); }; }; }
class TextProcessor { @validateMinLength(5) processText(text: string, options: { uppercase?: boolean } = {}) { let result = text; if (options.uppercase) { result = text.toUpperCase(); } return result; } }
|
6. 高级应用
6.1 多个装饰器组合
function logger() { return function(target: Object, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function(...args: any[]) { console.log(`调用方法 ${propertyKey}`, args); const result = originalMethod.apply(this, args); console.log(`方法 ${propertyKey} 完成`, result); return result; }; }; }
function measure(target: Object, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function(...args: any[]) { const start = performance.now(); const result = originalMethod.apply(this, args); const end = performance.now(); console.log(`${propertyKey} 执行时间: ${end - start}ms`); return result; }; }
class APIClient { @logger() @measure() async fetch(url: string, data: any) { await new Promise(resolve => setTimeout(resolve, 1000)); return { status: 'success', data }; } }
|
6.2 装饰器工厂模式
function createValidator(rules: Array<(value: any) => boolean>) { return function(target: Object, propertyKey: string) { let value = target[propertyKey]; const getter = function() { return value; }; const setter = function(newVal: any) { for (const rule of rules) { if (!rule(newVal)) { throw new Error(`验证失败: ${propertyKey} 不符合规则`); } } value = newVal; }; Object.defineProperty(target, propertyKey, { get: getter, set: setter, enumerable: true, configurable: true }); }; }
function isEmail(value: any): boolean { return typeof value === 'string' && value.includes('@'); }
function isRequired(value: any): boolean { return value !== null && value !== undefined && value !== ''; }
class UserForm { @createValidator([isRequired, isEmail]) email: string; @createValidator([isRequired]) username: string; constructor(email: string, username: string) { this.email = email; this.username = username; } }
|
6.3 自定义装饰器:缓存
function cacheable(target: Object, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; const cache = new Map(); descriptor.value = function(...args: any[]) { const cacheKey = JSON.stringify(args); if (cache.has(cacheKey)) { console.log(`从缓存获取结果: ${propertyKey}`); return cache.get(cacheKey); } console.log(`计算新结果: ${propertyKey}`); const result = originalMethod.apply(this, args); cache.set(cacheKey, result); return result; }; target.clearCache = function() { cache.clear(); }; }
class FibonacciCalculator { @cacheable calculate(n: number): number { if (n <= 1) return n; return this.calculate(n - 1) + this.calculate(n - 2); } }
const calculator = new FibonacciCalculator(); console.log(calculator.calculate(10)); console.log(calculator.calculate(10)); calculator.clearCache();
|
7. 最佳实践
7.1 装饰器使用原则
- 保持简单:装饰器应该简单、专注、可复用
- 避免副作用:装饰器不应修改类的核心逻辑
- 文档化:为自定义装饰器提供清晰的文档
- 测试覆盖:为装饰器编写单元测试
7.2 性能考虑
function memoize(timeout: number = 5000) { const cache = new Map(); return function(target: Object, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function(...args: any[]) { const cacheKey = JSON.stringify(args); const now = Date.now(); if (cache.has(cacheKey)) { const { value, timestamp } = cache.get(cacheKey); if (now - timestamp < timeout) { return value; } } const result = originalMethod.apply(this, args); cache.set(cacheKey, { value: result, timestamp: now }); return result; }; }; }
class DataCache { @memoize(3000) async fetchUserData(userId: string) { console.log(`从数据库获取用户 ${userId}`); await new Promise(resolve => setTimeout(resolve, 1000)); return { id: userId, name: `User ${userId}` }; } }
|
7.3 装饰器组合策略
function createDecorator(name: string) { return function(target: Object, propertyKey: string, descriptor: PropertyDescriptor) { console.log(`应用装饰器: ${name}`); return descriptor; }; }
function combine(...decorators: Function[]) { return function(target: Object, propertyKey: string, descriptor: PropertyDescriptor) { let result = descriptor; for (const decorator of decorators) { result = decorator(target, propertyKey, result) || result; } return result; }; }
function withLogging() { return createDecorator('Logging'); }
function withValidation() { return createDecorator('Validation'); }
function withPerformance() { return createDecorator('Performance'); }
class APIService { @combine( withLogging(), withValidation(), withPerformance() ) async getData(url: string) { console.log('API调处理中...'); return { data: 'success' }; } }
|
8. 实际案例:完整的装饰器系统
8.1 实体管理系统
@Entity('users') class User { @PrimaryColumn() id: number; @Column({ unique: true }) username: string; @Column() email: string; @Column({ default: 'active' }) status: string; }
function Entity(tableName: string) { return function(constructor: Function) { constructor.prototype._tableName = tableName; }; }
function Column(options: { unique?: boolean, default?: any } = {}) { return function(target: Object, propertyKey: string) { if (!target['_columns']) { target['_columns'] = {}; } target['_columns'][propertyKey] = options; }; }
function PrimaryColumn() { return Column(); }
class EntityManager { static save<T>(entity: T) { const tableName = (entity as any)._tableName; const columns = (entity as any)['_columns'] || {}; console.log(`Saving ${tableName} with columns:`, Object.keys(columns)); return true; } }
const user = new User(); user.id = 1; user.username = 'john_doe'; user.email = 'john@example.com';
EntityManager.save(user);
|
8.2 依赖注入系统
const DIContainer = new Map();
function Injectable() { return function(constructor: Function) { DIContainer.set(constructor, new constructor()); }; }
function Inject(token: string | Function) { return function(target: Object, propertyKey: string, parameterIndex: number) { const originalMethod = target[propertyKey]; target[propertyKey] = function(...args: any[]) { const dependency = typeof token === 'string' ? DIContainer.get(token) : DIContainer.get(token); args[parameterIndex] = dependency; return originalMethod.apply(this, args); }; }; }
@Injectable() class DatabaseService { connect() { console.log('Database connected'); } }
@Injectable() class UserService { constructor( @Inject(DatabaseService) private dbService: DatabaseService ) {} getUsers() { this.dbService.connect(); return ['user1', 'user2']; } }
const userService = DIContainer.get(UserService); console.log(userService.getUsers());
|
9. 调试和问题解决
9.1 装饰器调试技巧
function debug(target: Object, propertyKey: string | symbol, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function(...args: any[]) { console.group(`调试: ${String(propertyKey)}`); console.log('参数:', args); try { const result = originalMethod.apply(this, args); console.log('返回值:', result); console.groupEnd(); return result; } catch (error) { console.error('错误:', error); console.groupEnd(); throw error; } }; return descriptor; }
class DebugExample { @debug process(data: any) { if (!data) { throw new Error('数据不能为空'); } return data * 2; } }
|
9.2 常见问题及解决方案
问题1:装饰器执行顺序
function log1(target: Object, propertyKey: string) { console.log('装饰器1'); }
function log2(target: Object, propertyKey: string) { console.log('装饰器2'); }
class Example { @log2 @log1 method() {} }
|
问题2:装饰器中的this绑定
function bind(target: Object, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; descriptor.value = function(...args: any[]) { return originalMethod.apply(this, args); }; }
class BoundExample { data = 42; @bind getValue() { return this.data; } }
const example = new BoundExample(); const getValue = example.getValue; console.log(getValue());
|
10. 总结
装饰器是TypeScript中一个非常强大的特性,它可以帮助我们:
- 实现横切关注点:如日志、性能监控、权限控制等
- 提高代码复用性:通过可复用的装饰器逻辑
- 增强可维护性:将相关逻辑集中管理
- 减少样板代码:简化常见的重复性任务
在实际项目中,合理使用装饰器可以显著提高代码质量和开发效率。但同时也要注意:
- 不要过度使用装饰器
- 保持装饰器的简单和专注
- 为团队提供清晰的文档和使用指南
通过本文的学习,你应该已经掌握了TypeScript装饰器的基本用法和高级技巧,能够在实际项目中灵活运用这些知识来构建更加优雅和可维护的代码。