TypeScript装饰器学习(一):概念介绍、类装饰器

1,137 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

1. 定义

装饰器就是一个函数,可以注入到类,方法,属性,参数,对象上,对其功能进行拓展。根据装饰器所在位置。可以将装饰器分为:类装饰器,方法装饰器,参数装饰器,元数据装饰器。其他都好理解,但是元数据装饰器又是啥呢?

  • 元数据装饰器:元数据是用来描述数据的数据,在定义类、方法时,给他们定义描述信息。TypeScript中可以引入reflect-metadata这个第三方库,调用@Reflect.metadata来定义元数据。如下:
    @Reflect.metadata('info', '一些信息')
    

2. 类装饰器

  1. 环境搭建 tsconfig.json中,允许装饰器
    // 允许普通装饰器
    "experimentalDecorators": true,
    
    // 允许元数据装饰器
    "emitDecoratorMetadata": true,  
    
  2. 示例
    • 不带参数

      function CustomerDecorator(targetClass: any) {
          let customer = new targetClass('张三');
          customer.buy()
      }
      
      @CustomerDecorator
      class CustomerServeice {
        name: string;
        constructor(name: string) { 
          this.name = name
        }
        buy() {
          console.log(`${this.name}购买`);
        }
      
        pay() {
          console.log(`${this.name}付款`);
        }
      }
      

      此时控制台输出张三购买,此种情况为调用装饰器不带参数,默认传给装饰器方法的是类本身。

    • 带参数

      function CustomerDecorator(params: string) {
        return function (targetClass: any) {
          console.log(params);
          let customer = new targetClass();
          customer.buy()
        }
      }
      
      @CustomerDecorator('带参类装饰器')
      class CustomerServeice {
        name: string = "张三"
        constructor() { }
        buy() {
          console.log(`${this.name}购买`);
        }
      
        pay() {
          console.log(`${this.name}付款`);
        }
      }
      

      此时控制台输出带参类装饰器 张三购买 那么这里是这么实现的呢? 下面让我们看看通过tsc生成的JS源码

    • 装饰器JS源码

       /**
       * @param  {Array} decorators - 装饰器数组,可以为一个类或函数添加多个装饰器
       * @param  {} target - 装饰器所装饰的目标本身
       * @param  {} key
       * @param  {} desc
       */
       var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
         // 获取参数个数
         var argLength = arguments.length;
      
         // targetInfo装饰器最终所装饰的目标本身
         // 参数个数为2,所装饰的是类/构造器参数,targetInfo=target(即参数target,类本身)
         // 参数个数为4,所装饰的是方法【参数desc等于null】,targetInfo为该方法的数据属性
         // 参数个数为4,所装饰的是方法参数或属性,targetInfo为undefined
         var targetInfo = argLength < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc;
      
         // decorator用来保存装饰器数组元素
         var decorator;
      
         if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
           // 判断是否为元数据
           targetInfo = Reflect.decorate(decorators, target, key, desc);
         } else {
           // 遍历装饰目标上的所有装饰器,执行顺序为从下往上
           for (var i = decorators.length - 1; i >= 0; i--) {
             if (decorator = decorators[i]) {
               // 如果参数个数少于3【decorator为类装饰器或构造器参数装饰器】,执行decorator(targetInfo)
               // 如果参数个数大于3【decorator为方法装饰器】,执行decorator(target, key, targetInfo)
               // 如果参数个数等于于3【decorator为方法参数装饰器或属性装饰器】,执行decorator(target, key))
               // targetInfo为最终装饰器执行后返回值
               targetInfo = (argLength < 3 ? decorator(targetInfo) : argLength > 3 ? decorator(target, key, targetInfo) : decorator(target, key)) || targetInfo
             };
           }
         }
         return argLength > 3 && targetInfo && Object.defineProperty(target, key, targetInfo), targetInfo;
       };
      
       function CustomerDecorator(params) {
         return function (targetClass) {
           console.log(params);
           var customer = new targetClass();
           customer.buy();
         };
       }
       var CustomerServeice = /** @class */ (function () {
         function CustomerServeice() {
           this.name = "张三";
         }
         CustomerServeice.prototype.buy = function () {
           console.log(this.name + "\u8D2D\u4E70");
         };
         CustomerServeice.prototype.pay = function () {
           console.log(this.name + "\u4ED8\u6B3E");
         };
      
         // 类装饰器只传入两个参数给__decorate
         CustomerServeice = __decorate([
           // 带参数的先执行函数,获得返回的方法
           CustomerDecorator('带参类装饰器')
         ], CustomerServeice);
         return CustomerServeice;
       }());
      
  3. 应用
 /**
 * 需求:对已经开发好的项目中的任何一个类创建实例时
 * 打印日志信息:输出哪一个类被创建了,并输出传递了哪些参数
 */
 function LoggerDecorator<T extends { new(...args: any): any }>(targetClass: T) {
   return class extends targetClass {
     constructor(...args: any) {
       super(...args)
       console.log(targetClass.name);
       console.log(args);
     }
   }
 }

 @LoggerDecorator
 class Person {
   name: string
   constructor(name: string) {
     this.name = name
   }
 }

 let p1 = new Person('张三')

此时控制台输出 Person [ '张三' ]

注意:当类装饰器方法为不带参是,类装饰器方法的返回值为void|typeof 类目标,返回的必须是空或者目标类的子类