𝑻𝒆𝒏𝑪𝒍𝒂𝒘正在头脑风暴···
𝑻𝒆𝒏𝑲𝒊𝑺𝒆𝒀𝒂の𝑨𝒈𝒆𝒏𝒕助手
𝑻𝒆𝒏-𝒇𝒍𝒂𝒔𝒉

TypeScript装饰器应用实战

前言

装饰器是TypeScript中一个强大的元编程特性,它允许我们在不修改目标类或函数源代码的情况下,动态地扩展其功能。本文将深入探讨TypeScript装饰器的概念、语法、使用场景,并通过实际案例展示如何利用装饰器编写更加优雅、可维护的代码。

1. 装饰器基础

1.1 什么是装饰器

装饰器是一种特殊类型的声明,可以附加到类、方法、属性或参数上。它本质上是一个函数,在运行时被调用,接收被装饰的目标作为参数,并可以对其进行修改或增强。

1.2 启用装饰器支持

在TypeScript中,装饰器默认是关闭的。我们需要在tsconfig.json中启用:

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

1.3 装饰器类型

TypeScript支持以下几种装饰器:

  1. 类装饰器 - 应用于类
  2. 方法装饰器 - 应用于方法
  3. 属性装饰器 - 应用于属性
  4. 参数装饰器 - 应用于参数

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 };
}
}

// 输出:
// Class UserService created
// Class details: [class UserService]

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'); // 正确
// const invalidProfile = new UserProfile('invalid-email'); // 抛出错误

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); // 12345-67890
// config.API_KEY = 'new-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) {
// 模拟API调用
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 装饰器使用原则

  1. 保持简单:装饰器应该简单、专注、可复用
  2. 避免副作用:装饰器不应修改类的核心逻辑
  3. 文档化:为自定义装饰器提供清晰的文档
  4. 测试覆盖:为装饰器编写单元测试

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) // 3秒缓存
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();
}

// ORM工具类
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() {}
}

// 输出:
// 装饰器1
// 装饰器2

问题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()); // 正确返回42

10. 总结

装饰器是TypeScript中一个非常强大的特性,它可以帮助我们:

  1. 实现横切关注点:如日志、性能监控、权限控制等
  2. 提高代码复用性:通过可复用的装饰器逻辑
  3. 增强可维护性:将相关逻辑集中管理
  4. 减少样板代码:简化常见的重复性任务

在实际项目中,合理使用装饰器可以显著提高代码质量和开发效率。但同时也要注意:

  • 不要过度使用装饰器
  • 保持装饰器的简单和专注
  • 为团队提供清晰的文档和使用指南

通过本文的学习,你应该已经掌握了TypeScript装饰器的基本用法和高级技巧,能够在实际项目中灵活运用这些知识来构建更加优雅和可维护的代码。