TypeScript装饰器:从入门到精通
什么是装饰器?
装饰器(Decorator)是TypeScript中一个非常酷的特性,它允许我们在不修改原有代码的情况下,给类、方法、属性等添加额外的功能。想象一下装饰器就像给你的代码"穿衣服",可以一层层地叠加功能,而不会破坏原有的结构。
在ES2016中,装饰器还只是一个提案,但TypeScript已经提前实现了这个功能。要使用装饰器,需要在tsconfig.json
中开启experimentalDecorators
选项。
装饰器基础
类装饰器
类装饰器是最简单的一种装饰器,它接收一个构造函数作为参数。让我们看个例子:
function logClass(target: Function) {
console.log(`类 ${target.name} 被装饰了`);
}
@logClass
class MyClass {
constructor() {
console.log('创建MyClass实例');
}
}
const myClass = new MyClass();
// 输出:
// 类 MyClass 被装饰了
// 创建MyClass实例
方法装饰器
方法装饰器可以拦截方法的调用,非常适合用于日志记录、权限验证等场景:
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`调用方法 ${propertyKey},参数: ${JSON.stringify(args)}`);
const result = originalMethod.apply(this, args);
console.log(`方法 ${propertyKey} 返回: ${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);
// 输出:
// 调用方法 add,参数: [2,3]
// 方法 add 返回: 5
装饰器工厂
有时候我们希望装饰器能接收参数,这时候就需要使用装饰器工厂:
function logWithPrefix(prefix: string) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
console.log(`[${prefix}] 调用方法 ${propertyKey}`);
return originalMethod.apply(this, args);
};
return descriptor;
};
}
class Logger {
@logWithPrefix('DEBUG')
log(message: string) {
console.log(message);
}
}
const logger = new Logger();
logger.log('这是一条日志');
// 输出:
// [DEBUG] 调用方法 log
// 这是一条日志
装饰器的执行顺序
当多个装饰器应用于同一个声明时,它们的执行顺序很重要:
- 参数装饰器,然后是方法、访问器或属性装饰器
- 装饰器从最靠近声明的装饰器开始执行
- 类装饰器最后执行
function first() {
console.log('first() 工厂函数');
return function(target: any) {
console.log('first() 装饰器');
};
}
function second() {
console.log('second() 工厂函数');
return function(target: any) {
console.log('second() 装饰器');
};
}
@first()
@second()
class ExampleClass {}
// 输出:
// first() 工厂函数
// second() 工厂函数
// second() 装饰器
// first() 装饰器
装饰器的实际应用
1. 自动绑定this
在React类组件中,我们经常需要绑定方法的this指向,装饰器可以帮我们自动完成:
function autobind(_: any, _2: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const adjDescriptor: PropertyDescriptor = {
configurable: true,
enumerable: false,
get() {
const boundFn = originalMethod.bind(this);
return boundFn;
}
};
return adjDescriptor;
}
class Button {
@autobind
onClick() {
console.log('按钮被点击', this);
}
}
const button = new Button();
document.addEventListener('click', button.onClick);
2. 表单验证
装饰器可以很方便地实现表单验证逻辑:
function validate(min: number, max: number) {
return function(target: any, propertyKey: string) {
let value: number;
const getter = function() {
return value;
};
const setter = function(newVal: number) {
if (newVal < min || newVal > max) {
throw new Error(`值必须在 ${min} 和 ${max} 之间`);
}
value = newVal;
};
Object.defineProperty(target, propertyKey, {
get: getter,
set: setter
});
};
}
class User {
@validate(1, 120)
age: number;
}
const user = new User();
user.age = 25; // 正常
user.age = 0; // 抛出错误
装饰器的局限性
虽然装饰器很强大,但也有一些限制:
- 不能装饰函数声明(只能装饰类、方法、访问器、属性或参数)
- 装饰器不能修改类的结构(比如不能添加或删除类成员)
- 目前还是实验性特性,未来可能会有变化
总结
TypeScript装饰器是一个非常强大的元编程工具,它允许我们以声明式的方式为代码添加功能。通过本文的介绍,你应该已经掌握了:
- 装饰器的基本概念和使用方法
- 类装饰器、方法装饰器、属性装饰器的区别
- 如何创建装饰器工厂
- 装饰器的执行顺序
- 装饰器在实际项目中的应用场景
装饰器在Angular、NestJS等框架中都有广泛应用,掌握好这个特性,能让你的代码更加优雅和可维护。