概述

  • 面向对象的概念(java:注解,c#:特征),decorator

  • angular 大量使用,react 中也会用到;

  • 目前 JS 支持装饰器,目前处于建议征集的第二阶段;

解决的问题

  1. 现在要做一个事情,对下面代码中的每一个属性进行验证,按照现在的 ts 的方式,可以这么做:

    class A {
        constructor(
            private prop1: string,
            private prop2: number,
            private prop3: boolean,
            private prop4: string,
            private prop5: number,
            private prop6: boolean,
            // ... 这里还有有100个属性
        ) { }
        /**
        *验证函数
        */
        public validate() {
            if (!this.prop1 && this.prop1.length < 10) {
                // 验证prop1
            }
            if (this.prop2 > 0) {
    
            }
            if (!this.prop3) {
    
            }
            // ...
        }
    }
    
  2. 看上面的验证代码,可以发现一些不合理的地方:

    • 每一个属性都要验证,就要写一个 if 来进行判断,100 个属性就要写 100 遍,然后每一个 if 中的条件还有可能相同,就会导致写重复的代码,这是非常麻烦的;
    • 写一个类的属性的时候,才是最清楚这个成员的条条框框,过了一会儿在写,可能都不记得;换一句话说是,类的成员与成员验证分开是不易于代码的维护的;
  3. 装饰器,能够带来额外的信息量,可以达到分离关注点的目的:

    • 信息书写位置的问题
    • 重复代码的问题

基于上面的这些因素,js 就提出了 装饰器的概念(现在依旧还没有正式发布),有了装饰器后,让前端的 js 类的代码更像 java 等后端语言了;但是要清楚的是,装饰器是 js 中提出的,并不是 ts 中的特殊语法;

装饰器的本质

  1. 上述两个问题产生的根源:某些信息,在定义时,能够附加的信息量有限;

  2. 装饰器的作用:为某些属性、类、参数、方法提供元数据信息(元数据:描述数据的数据);

  3. JS 中,装饰器是一个函数(装饰器是要参与运行的);

  4. 装饰器可以修饰:

    • 成员(属性+方法)
    • 参数

类装饰器

  1. 类装饰器的本质是一个函数,该函数接收一个参数,表示类本身(构造函数本身);装饰器函数的运行时间:在类定义后直接运行

  2. TS 中,有两种方式来约束一个变量的类型是一个类:

    • Function(这种方式不推荐,范围太大了);
    • new (...args: any[])=>object (推荐);
  3. TS 中要使用装饰器,需要开启 experimentalDecorators: true(实验阶段的装饰器);

  4. 类装饰器可以具有的返回值:

    • void:仅运行函数
      function test(target: new (...args: any[]) => object) {
          console.log(target); // [Function: A]
      }
      
      @test
      class A {
          constructor(public prop1: string, public prop2: string) {
              console.log(prop1, prop2);
          }
      }
      
      new A('你好', 'ts'); // 你好 ts
      
    • 返回一个新的类:会将新的类替换掉装饰目标
      function test(str: string) {
          console.log(str); // 这是一个类
          return function (target: new (...args: any[]) => object) { }
      }
      
      @test("这是一个类")
      class A {
          constructor(public prop1: string, public prop2: string) {
              console.log(prop1, prop2);
          }
      }
      
      new A('你好', 'ts'); // 你好 ts
      
  5. 多个装饰器的情况:

    • 会按照后加入先调用的顺序进行调用;
      type constructor = new (...args: any[]) => object;
      function d1() {
          console.log('d1')
          return function (target: constructor) {
              console.log('d1 Decorators');
          }
      }
      function d2() {
          console.log('d2')
          return function (target: constructor) {
              console.log('d2 Decorators');
          }
      }
      
      @d1()
      @d2()
      class A {
      
      }
      
      // 输出:
      // d1
      // d2
      // d2 Decorators
      // d1 Decorators
      
    • 编译后的代码:

成员装饰器

  1. 属性装饰器 也是一个函数,该函数需要两个参数:

    • 参数1:如果是静态属性,则为类本身;如果是实例属性,则为类的原型;
    • 参数2:固定为一个字符串,表示属性名
    TypeScript
    TypeScript
    function d(target: any, key: string) {
        console.log(target, key);
    }
    
    class A {
        @d
        prop1: string; // {} prop1
        @d
        static prop2: string; // [Function: A] prop2
    }
    
    function d() {
        return function d(target: any, key: string) {
            console.log(target, key);
        }
    }
    
    class A {
        @d()
        prop1: string; // {} prop1
        @d()
        static prop2: string; // [Function: A] prop2
    }
    
  2. 方法装饰器 也是一个函数,该函数需要三个参数:

    • 参数1:如果是静态方法,则为类本身;如果是实例方法,则为类的原型;
    • 参数2:固定为一个字符串,表示方法名
    • 参数3:属性描述对象
    function d() {
        return function d(target: any, key: string, descriptor: PropertyDescriptor) {
            console.log(target);
            console.log(key);
            console.log(descriptor);
            // {} 
            // method1 
            // {
            //     value: [Function: method1],
            //     writable: true,
            //     enumerable: false,
            //     configurable: true
            // }
        }
    }
    
    class A {
        @d()
        method1() { }
    }
    
  3. 可以有多个装饰器修饰

    // 可以被枚举
    function enumerable(target: any, key: string, descriptor: PropertyDescriptor) {
        // console.log(target, key, descriptor);
        descriptor.enumerable = true;
    }
    
    // 废除方法,不影响之前的使用
    function useless(target: any, key: string, descriptor: PropertyDescriptor) {
        descriptor.value = function () {
            console.warn(key + "方法已过期");
        }
    }
    
    class A {
        @enumerable
        @useless
        method1() {
            console.log("method1");
        }
    
        @enumerable
        method2() {
        }
    }
    
    const a = new A();
    a.method1(); // method1方法已过期
    
    for (const key in a) {
        console.log(key);
        // method1
        // method2
    }
    

装饰器练习

  1. Descriptor.ts

    export function classDescriptor(description: string) {
        return function (target: Function) {
            //保存到该类的原型中
            target.prototype.$classDescription = description;
        }
    }
    
    export function propDescriptor(description: string) {
        return function (target: any, propName: string) {
            //把所有的属性信息保存到该类的原型中
            if (!target.$propDescriptions) {
                target.$propDescriptions = [];
            }
            target.$propDescriptions.push({
                propName,
                description
            });
        }
    }
    
    export function printObj(obj: any) {
        //输出类的名字
        if (obj.$classDescription) {
            console.log(obj.$classDescription);
        }
        else {
            console.log(Object.getPrototypeOf(obj).constructor.name);
        }
        if (!obj.$propDescriptions) {
            obj.$propDescriptions = [];
        }
        //输出所有的属性描述和属性值
        for (const key in obj) {
            if (obj.hasOwnProperty(key)) {
                const prop = obj.$propDescriptions.find((p: any) => p.propName === key);
                if (prop) {
                    console.log(`\t${prop.description}:${obj[key]}`)
                }
                else {
                    console.log(`\t${key}:${obj[key]}`)
                }
            }
        }
    }
    
  2. index.ts

    import { classDescriptor, propDescriptor, printObj } from "./Descriptor";
    
    @classDescriptor("文章")
    class Article {
    
        @propDescriptor("标题")
        title: string
    
        @propDescriptor("内容")
        content: string
    
        @propDescriptor("日期")
        date: Date
    }
    
    
    const ar = new Article();
    ar.title = "xxxx";
    ar.content = "asdfasdfasdfasdfasdf";
    ar.date = new Date();
    
    printObj(ar);
    

参数装饰器

  1. 参数装饰器:依赖注入、依赖倒置

  2. 要求函数有三个参数:

    • 如果方法是静态的,则为类本身;如果方法是实例方法,则为类的原型
    • 方法名称或者 undefined
    • 在参数列表中的索引
    function d(params: string) {
        return function (target: any, prop: string, desc: any) {
            console.log(target, prop, desc);
        }
    }
    class A {
        test(@d('参数1') prop1: string, @d('参数2') prop2: string) { }
        // {} test 1
        // {} test 0
    
        static test2(@d('参数1') prop1: string, @d('参数2') prop2: string) { }
        // [class A] test2 1
        // [class A] test2 0
    }
    

第三方库

reflect-metadata 库

该库的作用:保存元数据

如果安装了 reflect-metadata,并且导入了该库,并且在某个成员上添加了元数据,并且启用了 emitDecoratorMetadata: true;则 TS 在编译结果中,会将约束的类型,作为元数据加入到相应位置;

class-validator 库

这个库是对类进行验证的,需要依赖于 reflect-metadata

class-transformer 库

把一维的对象转成类,需要依赖于 reflect-metadata

打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于产品、设计、开发的问题和看法,还有技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程

了解更多

目录

  1. 1. 概述
    1. 1.1. 解决的问题
    2. 1.2. 装饰器的本质
  2. 2. 类装饰器
  3. 3. 成员装饰器
  4. 4. 装饰器练习
  5. 5. 参数装饰器
  6. 6. 第三方库
    1. 6.1. reflect-metadata 库
    2. 6.2. class-validator 库
    3. 6.3. class-transformer 库