装饰器基础与应用
装饰器是TypeScript中一种强大的语法特性,允许在不修改原始代码的情况下,通过注解的方式扩展类、方法、属性或参数的行为。它们广泛应用于日志记录、性能分析、依赖注入等场景,能够显著提升代码的可维护性和复用性。
装饰器的基本概念
装饰器本质上是一个函数,它接收特定的参数并返回一个新的函数或修改后的目标。TypeScript中的装饰器分为以下几种类型:
- 类装饰器
- 方法装饰器
- 属性装饰器
- 参数装饰器
- 访问器装饰器
要使用装饰器,需要在tsconfig.json
中启用实验性装饰器支持:
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
类装饰器
类装饰器应用于类构造函数,可以用来观察、修改或替换类定义。它接收一个参数:类的构造函数。
function logClass(target: Function) {
console.log(`Class ${target.name} is defined`);
}
@logClass
class MyClass {
constructor() {
console.log('Creating instance');
}
}
// 输出: "Class MyClass is defined"
更实用的例子是给类添加元数据:
function addMetadata(metadata: object) {
return function(target: Function) {
Reflect.defineMetadata('custom:metadata', metadata, target);
};
}
@addMetadata({ version: '1.0.0', author: 'John Doe' })
class ApiService {
// ...
}
const metadata = Reflect.getMetadata('custom:metadata', ApiService);
console.log(metadata); // { version: '1.0.0', author: 'John Doe' }
方法装饰器
方法装饰器用于类的方法,可以拦截方法调用、修改方法行为或添加额外功能。它接收三个参数:
- 对于静态成员是类的构造函数,对于实例成员是类的原型
- 成员名称
- 成员的属性描述符
function logMethod(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`Calling ${propertyKey} with args: ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`Method ${propertyKey} returned: ${result}`);
return result;
};
return descriptor;
}
class Calculator {
@logMethod
add(a: number, b: number): number {
return a + b;
}
}
const calc = new Calculator();
calc.add(2, 3);
// 输出:
// "Calling add with args: [2,3]"
// "Method add returned: 5"
属性装饰器
属性装饰器应用于类的属性,可以用来修改属性的行为或添加元数据。它接收两个参数:
- 对于静态成员是类的构造函数,对于实例成员是类的原型
- 成员名称
function formatDate(formatString: string) {
return function(target: any, propertyKey: string) {
let value: Date;
const getter = function() {
return value.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
};
const setter = function(newVal: Date) {
value = newVal;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter,
enumerable: true,
configurable: true
});
};
}
class Event {
@formatDate('MMMM Do YYYY')
date: Date;
constructor(date: Date) {
this.date = date;
}
}
const event = new Event(new Date());
console.log(event.date); // "June 15, 2023" (格式取决于当前日期)
参数装饰器
参数装饰器用于类构造函数或方法的参数,通常用于依赖注入或参数验证。它接收三个参数:
- 对于静态成员是类的构造函数,对于实例成员是类的原型
- 成员名称(如果是构造函数参数则为undefined)
- 参数在函数参数列表中的索引
function validateParam(min: number, max: number) {
return function(target: any, propertyKey: string, parameterIndex: number) {
const existingValidations: any[] = Reflect.getOwnMetadata('validations', target, propertyKey) || [];
existingValidations.push({
index: parameterIndex,
min,
max
});
Reflect.defineMetadata('validations', existingValidations, target, propertyKey);
};
}
function validate(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
const validations = Reflect.getOwnMetadata('validations', target, propertyKey) || [];
descriptor.value = function(...args: any[]) {
for (const validation of validations) {
const arg = args[validation.index];
if (arg < validation.min || arg > validation.max) {
throw new Error(`Parameter at index ${validation.index} must be between ${validation.min} and ${validation.max}`);
}
}
return originalMethod.apply(this, args);
};
return descriptor;
}
class MathOperations {
@validate
divide(
@validateParam(1, 100) dividend: number,
@validateParam(1, 10) divisor: number
): number {
return dividend / divisor;
}
}
const math = new MathOperations();
console.log(math.divide(50, 5)); // 10
console.log(math.divide(0, 5)); // 抛出错误
装饰器工厂
装饰器工厂是一个返回装饰器函数的函数,允许通过参数配置装饰器行为。
function logExecutionTime(threshold: number = 0) {
return function(
target: any,
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();
const duration = end - start;
if (duration > threshold) {
console.warn(`Method ${propertyKey} took ${duration.toFixed(2)}ms to execute (threshold: ${threshold}ms)`);
}
return result;
};
return descriptor;
};
}
class DataProcessor {
@logExecutionTime(10)
processLargeData() {
// 模拟耗时操作
for (let i = 0; i < 1000000; i++) {
Math.sqrt(i);
}
}
}
const processor = new DataProcessor();
processor.processLargeData(); // 如果执行时间超过10ms会输出警告
装饰器组合
多个装饰器可以同时应用于同一个目标,它们会按照特定顺序执行:
- 参数装饰器 → 方法装饰器 → 访问器装饰器 → 属性装饰器 → 类装饰器
- 同一类型的装饰器,从下到上执行
function first() {
console.log('first(): factory evaluated');
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('first(): called');
};
}
function second() {
console.log('second(): factory evaluated');
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log('second(): called');
};
}
class ExampleClass {
@first()
@second()
method() {}
}
// 输出顺序:
// "second(): factory evaluated"
// "first(): factory evaluated"
// "first(): called"
// "second(): called"
实际应用场景
1. API路由注册
const routes: any[] = [];
function Controller(prefix: string = '') {
return function(target: Function) {
Reflect.defineMetadata('prefix', prefix, target);
if (!Reflect.hasMetadata('routes', target)) {
Reflect.defineMetadata('routes', [], target);
}
routes.push(target);
};
}
function Get(path: string = '') {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const routes = Reflect.getMetadata('routes', target.constructor) || [];
routes.push({
method: 'get',
path,
handler: propertyKey
});
Reflect.defineMetadata('routes', routes, target.constructor);
};
}
@Controller('/users')
class UserController {
@Get('/')
getAll() {
return 'All users';
}
@Get('/:id')
getById(id: string) {
return `User ${id}`;
}
}
// 可以遍历routes数组来注册实际的路由
routes.forEach(controller => {
const prefix = Reflect.getMetadata('prefix', controller);
const controllerRoutes = Reflect.getMetadata('routes', controller);
controllerRoutes.forEach((route: any) => {
console.log(`Registering route: ${route.method.toUpperCase()} ${prefix}${route.path}`);
// 实际应用中这里会调用框架的路由注册方法
});
});
2. 表单验证
interface ValidationRule {
type: string;
message: string;
validator?: (value: any) => boolean;
}
function Validator(rules: ValidationRule[]) {
return function(target: any, propertyKey: string) {
Reflect.defineMetadata('validation', rules, target, propertyKey);
};
}
function validateForm(target: any) {
const properties = Object.getOwnPropertyNames(target);
properties.forEach(property => {
const rules = Reflect.getMetadata('validation', target, property) as ValidationRule[];
if (!rules) return;
const value = target[property];
for (const rule of rules) {
if (rule.type === 'required' && (value === undefined || value === null || value === '')) {
throw new Error(rule.message);
}
if (rule.validator && !rule.validator(value)) {
throw new Error(rule.message);
}
}
});
}
class UserForm {
@Validator([
{ type: 'required', message: 'Username is required' },
{
type: 'length',
message: 'Username must be between 3 and 20 characters',
validator: (value: string) => value.length >= 3 && value.length <= 20
}
])
username: string;
@Validator([
{ type: 'required', message: 'Email is required' },
{
type: 'pattern',
message: 'Invalid email format',
validator: (value: string) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)
}
])
email: string;
constructor(data: Partial<UserForm>) {
Object.assign(this, data);
}
validate() {
validateForm(this);
}
}
const form = new UserForm({
username: 'johndoe',
email: 'john@example.com'
});
form.validate(); // 验证通过
const invalidForm = new UserForm({
username: 'jo',
email: 'invalid-email'
});
try {
invalidForm.validate(); // 抛出验证错误
} catch (error) {
console.error(error.message);
}
3. 性能监控
function trackPerformance(metricName: string) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = async function(...args: any[]) {
const startMark = `${propertyKey}-start`;
const endMark = `${propertyKey}-end`;
performance.mark(startMark);
try {
const result = await originalMethod.apply(this, args);
return result;
} finally {
performance.mark(endMark);
performance.measure(metricName, startMark, endMark);
const measures = performance.getEntriesByName(metricName);
const lastMeasure = measures[measures.length - 1];
console.log(`[Performance] ${metricName}: ${lastMeasure.duration.toFixed(2)}ms`);
// 可以在这里将性能数据发送到监控系统
}
};
return descriptor;
};
}
class AnalyticsService {
@trackPerformance('fetchAnalyticsData')
async fetchData() {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, Math.random() * 1000));
return { data: 'sample' };
}
}
const analytics = new AnalyticsService();
analytics.fetchData(); // 输出性能数据
装饰器与元数据反射
TypeScript的装饰器可以与反射元数据API结合使用,实现更强大的功能。需要安装reflect-metadata
包:
npm install reflect-metadata
然后在应用入口导入:
import 'reflect-metadata';
示例:依赖注入容器
type Constructor<T = any> = new (...args: any[]) => T;
const Injectable = (): ClassDecorator => target => {};
class Container {
private instances = new Map<Constructor, any>();
register<T>(token: Constructor<T>, instance: T) {
this.instances.set(token, instance);
}
resolve<T>(token: Constructor<T>): T {
if (this.instances.has(token)) {
return this.instances.get(token);
}
const params = Reflect.getMetadata('design:paramtypes', token) || [];
const injections = params.map(param => this.resolve(param));
const instance = new token(...injections);
this.instances.set(token, instance);
return instance;
}
}
@Injectable()
class DatabaseService {
connect() {
console.log('Connected to database');
}
}
@Injectable()
class UserRepository {
constructor(private database: DatabaseService) {}
getUsers() {
this.database.connect();
return ['user1', 'user2'];
}
}
const container = new Container();
const userRepo = container.resolve(UserRepository);
console.log(userRepo.getUsers()); // ["user1", "user2"]
装饰器的局限性
虽然装饰器功能强大,但也存在一些限制和注意事项:
- 执行顺序:多个装饰器应用于同一目标时,执行顺序可能不符合直觉
- 调试困难:装饰器修改了原始行为,可能导致调试困难
- 性能影响:复杂的装饰器逻辑可能影响性能
- 类型信息:装饰器可能使类型系统复杂化,需要额外处理类型信息
- 提案阶段:装饰器目前仍是ECMAScript提案,未来可能有变化
高级技巧
动态装饰器应用
function conditionalDecorator(condition: boolean, decorator: MethodDecorator) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
if (condition) {
return decorator(target, propertyKey, descriptor);
}
return descriptor;
};
}
class FeatureToggle {
@conditionalDecorator(
process.env.NODE_ENV === 'development',
logMethod
)
experimentalMethod() {
// 只在开发环境记录日志
}
}
装饰器组合工具
function composeDecorators(...decorators: MethodDecorator[]) {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
return decorators.reduce((currentDescriptor, decorator) => {
return decorator(target, propertyKey, currentDescriptor) || currentDescriptor;
}, descriptor);
};
}
class MultiDecorated {
@composeDecorators(
logMethod,
trackPerformance('importantOperation')
)
importantOperation() {
// 同时应用日志和性能跟踪
}
}
装饰器与泛型
function typeCheck<T>() {
return function(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = function(arg: T) {
if (typeof arg !== typeof {} as T) {
throw new Error(`Invalid type: expected ${typeof {} as T}, got ${typeof arg}`);
}
return originalMethod.call(this, arg);
};
return descriptor;
};
}
class GenericExample {
@typeCheck<number>()
processNumber(num: number) {
return num * 2;
}
}
const example = new GenericExample();
console.log(example.processNumber(5)); // 10
console.log(example.processNumber('5' as any)); // 抛出类型错误
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:参数属性
下一篇:混入(Mixins)模式实现