TypeScript 装饰器是一种特殊类型的声明,它可以附加到类声明、方法、属性或参数上,以修改它们的行为。装饰器以 @decorator
的形式应用于类的声明、方法、属性或参数之前,可以传递参数,并且可以被嵌套使用。
TypeScript 装饰器可以用来解决一些重复性的问题,例如验证、性能监控、数据绑定等。通过装饰器,我们可以在不修改类代码的情况下,为类增加新的功能,使得代码更加灵活和可维护。
装饰器的分类
在 TypeScript 中,装饰器可以分为四种类型:
- 类装饰器
- 方法装饰器
- 属性装饰器
- 参数装饰器
类装饰器
类装饰器在类声明之前声明,以 @decorator
的形式应用于类声明之前,可以用来修改类的行为。类装饰器接收一个参数,它是一个指向类的构造函数的引用,可以用来访问类的元数据。
下面是一个简单的类装饰器示例:
function logClass(target: any) {
console.log(target);
}
@logClass
class Foo {
constructor() {}
}
在上面的代码中,logClass
装饰器被应用于 Foo
类之前,它会打印出 Foo
的构造函数。
方法装饰器
方法装饰器应用于类中的方法上,可以用来修改方法的行为。方法装饰器接收三个参数:
target
:对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象。propertyKey
:被装饰的方法的名称。descriptor
:被装饰的方法的属性描述符。
下面是一个简单的方法装饰器示例:
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(`${propertyKey} is decorated`);
}
class Foo {
@log
sayHello() {
console.log('Hello!');
}
}
在上面的代码中,log
装饰器被应用于 sayHello
方法之前,它会打印出 sayHello
被装饰。
属性装饰器
属性装饰器应用于类中的属性上,可以用来修改属性的行为。属性装饰器接收两个参数:
-
target
:对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象。 -
propertyKey
:被装饰的属性的名称。
下面是一个简单的属性装饰器示例:
function log(target: any, propertyKey: string) {
console.log(`${propertyKey} is decorated`);
}
class Foo {
@log
bar: string = 'Hello';
}
在上面的代码中,log
装饰器被应用于 bar
属性之前,它会打印出 bar
被装饰。
参数装饰器
参数装饰器应用于类中的方法参数上,可以用来修改方法参数的行为。参数装饰器接收三个参数:
target
:对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象。propertyKey
:被装饰的方法的名称。parameterIndex
:被装饰的参数的索引。
下面是一个简单的参数装饰器示例:
function log(target: any, propertyKey: string, parameterIndex: number) {
console.log(`${propertyKey} parameter ${parameterIndex} is decorated`);
}
class Foo {
sayHello(@log name: string) {
console.log(`Hello, ${name}!`);
}
}
在上面的代码中,log
装饰器被应用于 sayHello
方法的 name
参数之前,它会打印出 name
参数被装饰。
装饰器工厂
装饰器可以接受参数,这意味着您可以编写一个装饰器工厂函数,它将返回一个具体的装饰器。下面是一个装饰器工厂函数的示例:
function log(value: string) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
console.log(`${value}: ${propertyKey} is decorated`);
};
}
class Foo {
@log('bar')
sayHello() {
console.log('Hello!');
}
}
在上面的代码中,log
是一个装饰器工厂函数,它将接收一个参数 value
,并返回一个具体的装饰器。在 Foo
类中,@log('bar')
将应用于 sayHello
方法之前,log
装饰器将打印出 bar: sayHello is decorated
。
实际应用场景
TypeScript 装饰器可以用于很多场景,例如:
- 认证和授权:您可以使用装饰器来验证用户的身份,并授予用户不同的权限。
- 缓存和性能优化:您可以使用装饰器来缓存函数的结果,以提高性能和响应速度。
- 日志和调试:您可以使用装饰器来记录函数调用和响应,以便进行调试和分析。
- 数据绑定和响应式编程:您可以使用装饰器来创建响应式数据绑定,以便在数据发生变化时自动更新视图。
下面是一个实际应用场景的示例:
function Cache(duration: number) {
return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalMethod = descriptor.value;
const cache = new Map();
descriptor.value = function(...args: any[]) {
const key = `${propertyKey}_${JSON.stringify(args)}`;
const cachedValue = cache.get(key);
if (cachedValue && Date.now() - cachedValue.timestamp < duration) {
return cachedValue.value;
}
const result = originalMethod.apply(this, args);
cache.set(key, { value: result, timestamp: Date.now() });
return result;
};
return descriptor;
};
}
class Foo {
@Cache(5000)
fetchData(id: number): Promise<string> {
console.log(`fetchData(${id}) is called`);
return new Promise((resolve) => {
setTimeout(() => {
resolve(`data-${id}`);
}, 1000);
});
}
}
const foo = new Foo();
foo.fetchData(1).then((result) => console.log(result)); // fetchData(1) is called, data-1
foo.fetchData(1).then((result) => console.log(result)); // data-1
在上面的代码中,我们定义了一个 Cache
装饰器,它接受一个持续时间 duration
,并返回一个具体的装饰器。在 Foo
类中,我们将 @Cache(5000)
应用于 fetchData
方法之前,这意味着我们将缓存该方法的结果,并在 5 秒内返回缓存值,而不是重新执行方法。
在 fetchData
方法中,我们检查是否已经有了缓存值,并且缓存值的时间戳没有过期,如果有,则直接返回缓存值。否则,我们执行原始方法,并将结果存储在缓存中,并返回结果。
通过使用装饰器,我们可以轻松地为 fetchData
方法添加缓存功能,而不需要修改原始代码。这使得我们的代码更加灵活和可维护。
结论
TypeScript 装饰器是一种非常强大的功能,它可以帮助我们轻松地修改类、方法、属性和参数的行为,以满足不同的需求。通过装饰器,我们可以实现认证和授权、缓存和性能优化、日志和调试、数据绑定和响应式编程等多种功能,使得我们的代码更加灵活和可维护。
当然,装饰器并不是万能的,它也有一些缺点和限制。例如,装饰器只能用于静态编译时期,不能在运行时动态修改类的行为;装饰器的语法有时会比较复杂,需要一定的学习成本等。但是,总的来说,装饰器还是非常有价值的,可以帮助我们编写更加灵活、可维护和高效的代码。
原文链接:https://juejin.cn/post/7221094608055025724 作者:XinD