学习TS,装饰器在框架应用很广泛如:Nest.js,装饰器是必须掌握的内容,否则连入门都入不了。这里介绍TypeScript中装饰器的相关概念,适合初级小伙伴。
顺序为
- 属性 装饰器
- 参数 装饰器(普通方法)
- 方法 装饰器
- 构造函数 中的 方法参数 装饰器
- 类 装饰器
- 属性装饰器 是 从上而下;(同一方法内的)参数装饰器、(同一个类的)类装饰器 是 从后往前 执行
- 一个方法中,先执行参数装饰器(从后开始),再执行方法装饰器
装饰器概念
装饰器使用 @expression 的形式使用,expression 是一个函数,会在运行时被调用,被装饰的数据会作为参数传入到这个函数中。 代码中的 decorator 就是一个装饰器函数,接收一个 target 参数,decorator 装饰器修饰了 Animal 这个类,那么 Animal 类就被作为 target 参数传入到了 decorator 函数中。
function decorator(target: any) {
target.say = function () {
console.log("hello!");
};
}
@decorator
class Animal {
static say: Function;
constructor() {}
}
Animal.say(); // hello!
装饰器工厂
装饰器工厂通过 @expression(args) 形式使用,装饰器工厂中的 expression 会返回一个装饰器函数,args 是用户想自定义传入的参数。
function decorator(name: string) {
return function (target: any) {
target.say = function () {
console.log("My name is " + name);
};
};
}
@decorator("zhangsan")
class People1 {
static say: Function;
constructor() {}
}
People1.say(); // My name is zhangsan
@decorator("lisi")
class People2 {
static say: Function;
constructor() {}
}
People2.say(); // My name is lisi
多个装饰器的组合
可以对同一目标应用多个装饰器。
function decoratorName(name: string) {
return function (target: any) {
target.sayName = function () {
console.log("My name is " + name);
};
};
}
function decoratorAge(age: number) {
return function (target: any) {
target.sayAge = function () {
console.log("My age is " + age);
};
};
}
@decoratorName("zhangsan")
@decoratorAge(20)
class People1 {
static sayName: Function;
static sayAge: Function;
constructor() {}
}
People1.sayName(); // My name is zhangsan
People1.sayAge(); // My age is 20
装饰器类型
装饰器共有五种类型,分别为:1. 类装饰器;2.属性装饰器;3.方法装饰器;4.参数装饰器;5.访问器装饰器;
// 类装饰器
@classDecorator
class Bird {
// 属性装饰器
@propertyDecorator
name: string;
// 方法装饰器
@methodDecorator
fly(
// 参数装饰器
@parameterDecorator
meters: number
) {}
// 访问器装饰器
@accessorDecorator
get egg() {}
}
类装饰器
类装饰器在类声明之前被声明,用于类、构造函数,可以用来修改或添加类的属性和方法。
function decorator(param?: string): ClassDecorator {
return (target: any) => {
target.say = function () {
console.log("hello!");
};
target.run = function () {
console.log("I am running.");
};
};
}
@decorator()
class Animal {
static say: Function;
static run: Function;
constructor() {}
}
Animal.say(); // hello!
Animal.run(); // I am running.
属性装饰器
属性装饰器声明在一个属性声明之前,它允许你修改类的属性或者获取关于类属性的信息。属性装饰器接收两个参数:类的原型(即类本身)和属性键名。
// 定义一个属性装饰器
function log(target: any, key: string) {
let originalValue: any;
Object.defineProperty(target, key, {
get: function() {
console.log(`Getting ${key}: ${originalValue}`);
return originalValue;
},
set: function(value) {
console.log(`Setting ${key} to ${value}`);
originalValue = value;
},
enumerable: true,
configurable: true
});
}
// 使用属性装饰器
class Person {
@log
name: string;
constructor(name: string) {
this.name = name; // 这里会触发setter
}
}
// 创建Person实例
const person = new Person("Alice");
// 访问name属性
console.log(person.name); // 这里会触发getter
person.name = "Bob"; // 这里会再次触发setter
方法装饰器
方法装饰器用于装饰类的方法,它们可以拦截、修改或增强方法的行为。它接收三个参数:类的原型(即类本身)、方法的名称以及方法的描述符。 通过方法装饰器,将方法设置为不能改写。
// descriptor
{
value: [Function: originMethod],//属性值
writable: true,//是否可修改
enumerable: false,//是否可枚举
configurable: true //是否可删除
}
function decorator(): MethodDecorator {
return (target, propertyKey, descriptor) => {
descriptor.writable = false;
/*
// 修改函数体
let origin = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log("this:", this); // Person
// 迭代所有参数
args = args.map((arg) => arg + "增加相同内容")
console.log("args", args) // [ 'zh增加相同内容', 'llm增加相同内容' ]
// 调用以前的函数
origin.apply(this, args)
}
*/
};
}
class A {
@decorator()
originMethod() {
console.log("I'm Original!");
}
}
const a = new A();
try {
a.originMethod = () => {
console.log("I'm Changed!"); //Cannot assign to read only property 'originMethod'
};
} catch (e) {}
a.originMethod(); // I'm Original! 并没有被修改
参数装饰器
参数装饰器用于装饰类方法的参数。主要用于元数据的注入、参数的验证或者参数的转换。它接收三个参数:类的原型、方法名以及参数在参数列表中的位置。 通常用于收集参数信息,供其他装饰器使用。
function decorator(params?: any): ParameterDecorator {
return (target, propertyKey, index) => {
console.log(target); //{}
console.log(propertyKey); //say
console.log(index); //1
};
}
class Animal {
say(name: string, @decorator() age?: number) {}
}
const requiredMetadataKey = Symbol('required')
function Required(target: Object, propertyKey: string | symbol, parameterIndex: number) {
const requiredParameters: number[] = Reflect.getMetadata(requiredMetadataKey, target, propertyKey) || []
requiredParameters.push(parameterIndex)
Reflect.defineMetadata(requiredMetadataKey, requiredParameters, target, propertyKey)
}
function Validate(target: Object, propertyKey: string | symbol, descriptor: any) {
const originFn = descriptor.value
descriptor.value = function (...args: any[]) {
const requiredParameters: number[] = Reflect.getMetadata(requiredMetadataKey, target, propertyKey)
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
if ([undefined, null, ''].includes(args[parameterIndex])) {
throw new Error(`方法 ${String(propertyKey)} 缺少必填参数`)
}
}
}
return originFn.apply(this, args)
}
}
class ExampleClass4 {
@Validate
greet(@Required name: string) {
return `Hello, ${name}`
}
}
const ec = new ExampleClass4()
ec.greet('')
访问器装饰器
- 访问器装饰器类似于方法装饰器,唯一的区别在于描述器中有的key不同
- 方法装饰器的描述器的key为:value,writable,enumerable,configurable
- 访问器装饰器的描述器的key为:get,set,enumerable,configurable
function configurable(value: boolean) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
descriptor.configurable = value;
};
}
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
//不允许删除该属性
@configurable(false)
get x() {
return this._x;
}
//不允许删除该属性
@configurable(false)
get y() {
return this._y;
}
}
什么时候使用装饰器
- 通用 Before/After 钩子
- 监听属性变更或方法调用
- 转换方法参数
- 给类添加额外的方法或属性
- 运行时类型检查
- 自动编码/解码
- 依赖注入