美文网首页TypeScript
TypeScript的装饰器

TypeScript的装饰器

作者: 浅忆_0810 | 来源:发表于2020-10-13 23:00 被阅读0次

    装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,属性或参数上,可以修改类的行为,通俗来讲装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能

    装饰器已经是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. 装饰器的执行顺序

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

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

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

    相关文章

      网友评论

        本文标题:TypeScript的装饰器

        本文链接:https://www.haomeiwen.com/subject/wkgbpktx.html