TypeScript

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. 方法装饰器

方法装饰器被应用到方法的属性描述符上,可以用来监视,修改或替换方法定义,传入三个参数(都是自动传入的):

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. 方法参数装饰器

参数装饰器被表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元素数据,传入三个参数(都是自动传入的):

// 这个装饰器很少使用
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. 装饰器的执行顺序

装饰器的执行顺序大部分按照代码的执行顺序运行

如果有多个同样的装饰器,会从后到前依次执行

如果方法和方法参数装饰器在同一个方法出现,参数装饰器先执行

上一篇 下一篇

猜你喜欢

热点阅读