TypeScript 装饰器工厂详解
目录
基本概念
什么是装饰器工厂?
装饰器工厂是一个返回装饰器的函数,它可以接受参数来自定义装饰器的行为。通过装饰器工厂,我们可以创建更灵活和可配置的装饰器。函数能返回装饰器,就叫装饰器工厂
为什么使用装饰器工厂?
- 可以传递参数来配置装饰器
- 提供更大的灵活性
- 可以根据参数动态生成装饰器
- 支持复杂的装饰器逻辑
基本语法
1. 装饰器工厂基本结构
// 基本结构
function decoratorFactory(config: any) {
// 返回实际的装饰器函数
return function(target: any, propertyKey?: string, descriptor?: PropertyDescriptor) {
// 装饰器逻辑
};
}
// 使用装饰器工厂
@decoratorFactory({
// 配置参数
})
class Example {
// 类的内容
}
参数类型
1. 基本参数类型
// 单个参数
function Logger(prefix: string) {
return function(target: Function) {
// 使用 prefix 参数
console.log(`${prefix}: ${target.name}`);
};
}
// 多个参数
function Configure(id: string, options: { debug?: boolean; timeout?: number }) {
return function(target: Function) {
// 使用多个参数
console.log(`Configuring ${id} with`, options);
};
}
// 使用
@Logger('MyClass')
@Configure('component-1', { debug: true, timeout: 3000 })
class Example {}
2. 泛型参数
// 泛型装饰器工厂
function TypedDecorator<T>(config: T) {
return function<U extends { new(...args: any[]): {} }>(constructor: U) {
return class extends constructor {
_config: T = config;
};
};
}
interface Config {
apiUrl: string;
timeout: number;
}
@TypedDecorator<Config>({
apiUrl: 'https://api.example.com',
timeout: 5000
})
class ApiClient {}
返回值处理
1. 类装饰器返回值
function WithTemplate(template: string, hookId: string) {
return function<T extends { new(...args: any[]): { name: string } }>(
originalConstructor: T
) {
// 返回新的类
return class extends originalConstructor {
constructor(...args: any[]) {
super(...args);
const hookEl = document.getElementById(hookId);
if (hookEl) {
hookEl.innerHTML = template;
hookEl.querySelector('h1')!.textContent = this.name;
}
}
};
};
}
@WithTemplate('<h1>My Person Object</h1>', 'app')
class Person {
name = 'Max';
}
2. 方法装饰器返回值
function Measure(units: string = 'ms') {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
): PropertyDescriptor {
const originalMethod = descriptor.value;
// 返回新的属性描述符
return {
...descriptor,
value: function(...args: any[]) {
const start = performance.now();
const result = originalMethod.apply(this, args);
const finish = performance.now();
console.log(`${propertyKey} took ${finish - start}${units}`);
return result;
}
};
};
}
class Example {
@Measure('ms')
calculateSomething(iterations: number) {
// 复杂计算...
}
}
3. 属性装饰器返回值
function Format(formatString: string) {
return function(target: any, propertyKey: string): any {
// 属性描述符
let value: any;
// 返回属性描述符
return {
get() {
return `${formatString}: ${value}`;
},
set(newValue: any) {
value = newValue;
},
enumerable: true,
configurable: true
};
};
}
class User {
@Format('ID')
id: string = '123';
}
常见用途
1. 验证装饰器
function Validate(validationFn: (value: any) => boolean, errorMessage: string) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
if (!validationFn(args[0])) {
throw new Error(`Validation Error: ${errorMessage}`);
}
return originalMethod.apply(this, args);
};
return descriptor;
};
}
class UserService {
@Validate(
(age: number) => age >= 0 && age <= 120,
'Age must be between 0 and 120'
)
setAge(age: number) {
this.age = age;
}
}
2. 缓存装饰器
function Memoize(hashFunction?: (...args: any[]) => string) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
const cache = new Map<string, any>();
descriptor.value = function(...args: any[]) {
const key = hashFunction ? hashFunction(...args) : JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = originalMethod.apply(this, args);
cache.set(key, result);
return result;
};
return descriptor;
};
}
class MathUtils {
@Memoize()
fibonacci(n: number): number {
if (n <= 1) return n;
return this.fibonacci(n - 1) + this.fibonacci(n - 2);
}
}
3. 日志装饰器
function Log(options: {
level?: 'debug' | 'info' | 'warn' | 'error';
prefix?: string;
}) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
const { level = 'info', prefix = '' } = options;
descriptor.value = function(...args: any[]) {
console[level](`${prefix}[${propertyKey}] Called with:`, args);
const result = originalMethod.apply(this, args);
console[level](`${prefix}[${propertyKey}] Returned:`, result);
return result;
};
return descriptor;
};
}
class Service {
@Log({ level: 'debug', prefix: 'Service' })
getData(id: string) {
return { id, timestamp: Date.now() };
}
}
实际应用
1. API 请求装饰器
interface RequestConfig {
method: 'GET' | 'POST' | 'PUT' | 'DELETE';
url: string;
headers?: Record<string, string>;
}
function Request(config: RequestConfig) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = async function(...args: any[]) {
try {
const response = await fetch(config.url, {
method: config.method,
headers: {
'Content-Type': 'application/json',
...config.headers
},
body: config.method !== 'GET' ? JSON.stringify(args[0]) : undefined
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return originalMethod.call(this, data);
} catch (error) {
console.error(`Request failed:`, error);
throw error;
}
};
return descriptor;
};
}
class UserApi {
@Request({
method: 'GET',
url: 'https://api.example.com/users'
})
async getUsers(data: any) {
return data.map((user: any) => ({
...user,
fullName: `${user.firstName} ${user.lastName}`
}));
}
}
2. 权限控制装饰器
interface Role {
name: string;
permissions: string[];
}
function RequirePermission(permission: string) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
const user = getCurrentUser();
const hasPermission = user.roles.some(
(role: Role) => role.permissions.includes(permission)
);
if (!hasPermission) {
throw new Error(`Permission denied: ${permission} is required`);
}
return originalMethod.apply(this, args);
};
return descriptor;
};
}
class AdminPanel {
@RequirePermission('user.delete')
deleteUser(userId: string) {
// 删除用户逻辑
}
@RequirePermission('user.edit')
updateUser(userId: string, data: any) {
// 更新用户逻辑
}
}
最佳实践
1. 类型安全
// 使用泛型和接口确保类型安全
interface DecoratorConfig<T> {
validate?: (value: T) => boolean;
transform?: (value: T) => T;
errorMessage?: string;
}
function TypeSafeDecorator<T>(config: DecoratorConfig<T>) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
// 实现类型安全的装饰器逻辑
};
}
2. 错误处理
function SafeDecorator(errorHandler: (error: Error) => void) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
try {
return originalMethod.apply(this, args);
} catch (error) {
errorHandler(error as Error);
throw error;
}
};
return descriptor;
};
}
3. 组合装饰器
function compose(...decorators: Function[]) {
return function(target: any, key: string, descriptor: PropertyDescriptor) {
return decorators.reduce((acc, decorator) => {
return decorator(target, key, acc);
}, descriptor);
};
}
class Example {
@compose(
Log({ level: 'debug' }),
Validate(value => value > 0),
Memoize()
)
calculate(value: number) {
// 计算逻辑
}
}
总结
-
装饰器工厂的优点:
- 可配置性强
- 代码复用
- 类型安全
- 灵活性高
-
使用场景:
- 日志记录
- 性能监控
- 权限控制
- 数据验证
- 缓存处理
- API 请求处理
-
最佳实践:
- 保持简单
- 类型安全
- 错误处理
- 可组合性
- 参数验证
- 返回值处理