TypeScript的装饰器
2020-10-13 本文已影响0人
浅忆_0810
装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,属性或参数上,可以修改类的行为,通俗来讲装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能
装饰器已经是ES7
的标准特性之一
常见的装饰器
- 类装饰器
- 属性装饰器
- 方法装饰器
- 参数装饰器
装饰器的写法
- 普通装饰器(无法传参)
- 装饰器工厂(可传参)
因为装饰器只是个未来期待的用法,所以默认是不支持的,如果想要使用就要打开
tsconfig.json
中的experimentalDecorators
,否则会报语法错误
1. 类装饰器
类装饰器在类声明之前被声明(紧跟着类声明),类装饰器应用于类构造函数,可以用来监视,修改或替换类定义,需要传入一个参数
1.1 普通装饰器
function logClass(target: any) {
console.log(target);
// target就是当前类,在声明装饰器的时候会被默认传入
target.prototype.apiUrl = "动态扩展的属性";
target.prototype.run = function() {
console.log("动态扩展的方法");
};
}
@logClass
class HttpClient {
constructor() {}
getData() {}
}
// 这里必须要设置any,因为是装饰器动态加载的属性,所以在外部校验的时候并没有apiUrl属性和run方法
let http: any = new HttpClient();
console.log(http.apiUrl);
http.run();
1.2 装饰器工厂
如果要定制一个修饰器如何应用到一个声明上,需要写一个装饰器工厂函数。 装饰器工厂就是一个简单的函数,它返回一个表达式,以供装饰器在运行时调用
function color(value: string) { // 这是一个装饰器工厂
return function (target: any) { // 这是装饰器,这个装饰器就是上面普通装饰器默认传入的类
// do something with "target" and "value"...
}
}
function logClass(value: string) {
return function(target: any) {
console.log(target);
console.log(value);
target.prototype.apiUrl = value; // 将传入的参数进行赋值
};
}
@logClass("hello world") // 可传参数的装饰器
class HttpClient {
constructor() {}
getData() {}
}
let http: any = new HttpClient();
console.log(http.apiUrl);
1.3 类装饰器重构构造函数
类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数,如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明
/*
通过返回一个继承的类实现一个类的属性和方法的重构,换句话说就是在中间层有一个阻拦,然后返回的
是一个新的继承了父类的类,这个类必须有父类的所有属性和方法,不然会报错
*/
function logClass(target: any) {
return class extends target { // 可以当做是固定写法
apiUrl: string = "我是修改后的数据";
getData() {
console.log(this.apiUrl);
}
};
}
// 重构属性和方法
@logClass
class HttpClient {
constructor(public apiUrl = "我是构造函数中的数据") {}
getData() {
console.log(123);
}
}
let http: any = new HttpClient();
console.log(http.apiUrl); // 我是修改后的数据
http.getData(); // 我是修改后的数据
2. 属性装饰器
属性装饰器表达式在运行时当作函数被调用,传入两个参数(都是自动传入的):
- 对应静态成员来说是类的构造函数,对于实例成员来说是类的原型对象
- 成员的名字
function logProperty(value: string) {
return function(target: any, attr: string) {
// target为实例化的成员对象,attr为下面紧挨着的属性
console.log(target);
console.log(attr);
target[attr] = value; // 可以通过修饰器改变属性的值
};
}
class HttpClient {
@logProperty("hello world") // 修饰器后面紧跟着对应要修饰的属性
public url: string | undefined;
constructor() {}
getData() {
console.log(this.url);
}
}
let http: any = new HttpClient();
http.getData(); // hello world
3. 方法装饰器
方法装饰器被应用到方法的属性描述符上,可以用来监视,修改或替换方法定义,传入三个参数(都是自动传入的):
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 成员的名字(只是个
string
类型的字符串,没有其余作用) - 成员的属性描述符是一个对象,里面有真正的方法本身
function get(value: any) {
return function(target: any, methodName: any, desc: any) {
console.log(target); // HttpClient类
console.log(methodName); // getData方法名,一个字符串
console.log(desc); // 描述符
console.log(desc.value); // 方法本身就在desc.value中
target.url = 123; // 也能改变原实例
};
}
class HttpClient {
public url: any | undefined;
constructor() {}
@get("hello world")
getData() {
console.log(this.url);
}
}
let http = new HttpClient();
console.log(http.url); // 123
function get(value: any) {
return function(target: any, methodName: any, desc: any) {
let oMethod = desc.value;
desc.value = function(...args: any[]) {
// 因为用了方法装饰器,所以实际调用getData()方法的时候会调用desc.value来实现,通过赋值可以实现重构方法
// 原来的方法已经赋值给oMethod了,所以可以改变
args = args.map( // 这段代码是将传入的参数全部转换为字符串
(value: any): string => {
return String(value);
}
);
console.log(args); // 因为方法重构了,所以原来的getData()中的代码无效了,调用时会打印转换后参数
/*
如果想依然能用原来的方法,那么写入下面的代码,相当于就是对原来的方法进行了扩展
*/
oMethod.apply(target, args); // 通过这种方法调用可以也实现原来的getData方法
};
};
}
class HttpClient {
public url: any | undefined;
constructor() {}
@get("hello world")
getData(...args: any[]) {
console.log(args); // [ '1', '2', '3', '4', '5', '6' ]
console.log("我是getData中的方法");
}
}
let http = new HttpClient();
http.getData(1, 2, 3, 4, 5, 6); // [ '1', '2', '3', '4', '5', '6' ]
4. 方法参数装饰器
参数装饰器被表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元素数据,传入三个参数(都是自动传入的):
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
- 方法的名字(只是个
string
类型的字符串,没有其余作用) - 参数在函数参数列表中的索引
// 这个装饰器很少使用
function logParams(value: any) {
return function(target: any, methodName: any, paramsIndex: any) {
console.log(target);
console.log(methodName); // getData
console.log(paramsIndex); // 1,因为value在下面是第二个参数
};
}
class HttpClient {
public url: any | undefined;
constructor() {}
getData(index: any, @logParams("hello world") value: any) {
console.log(index);
console.log(value);
}
}
let http: any = new HttpClient();
http.getData(0, "123"); // 我是修改后的数据
5. 装饰器的执行顺序
装饰器的执行顺序大部分按照代码的执行顺序运行
如果有多个同样的装饰器,会从后到前依次执行
如果方法和方法参数装饰器在同一个方法出现,参数装饰器先执行