• 浅入浅出Typescript Decorators


    临时起的兴趣,想写一篇关于ts decorator的文章,就花小半天整理了一下...  这东西,在ES2017里好像也有... 文档的话看这里

    因为临时,就没想写太多文字介绍,带少许文字说明直接开撸代码吧。

    本文通过ts编译后的decorator代码结合两个案例来解释一番装饰器是什么?能做什么?有什么好处?

    实现代码

    编译后代码是这样的,带注释:

    var __decorate =
      (this && this.__decorate) ||
      function(decorators, target, key, desc) {
        // c 参数长度
        // r ? c < 3 则是target,否则先判断desc为null的话则将desc取target的key属性的描述,再否则便是desc了
        // d 预留使用
        var c = arguments.length,
          r =
            c < 3
              ? target
              : desc === null
              ? (desc = Object.getOwnPropertyDescriptor(target, key))
              : desc,
          d;
        // 下面文字解释,这仅是个甩锅的行为
        if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
          r = Reflect.decorate(decorators, target, key, desc);
        // 循环 decorators  并每次赋值给 d,并且判断值
        else
          for (var i = decorators.length - 1; i >= 0; i--)
            if ((d = decorators[i]))
              // c < 3 ,用 r 作为 decorators[i] 的入参执行;
              // c > 3 ,target, key, r 作为 decorators[i] 的入参执行;
              // c === 3,target, key 作为 decorators[i] 的入参执行。
              // 如果执行无返回结果, r = r;。
              r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
        // 如果 c > 3 && r , 修改 target ,返回 r
        return c > 3 && r && Object.defineProperty(target, key, r), r;
      };

    从代码里可以看出,最终结果要么是用 decorator 执行 target ,从而改变一些什么东西;要么就是使用 Object.defineProperty 来对 target 来做操作,代码就几行,用处确不小... 具体的执行过程结合下面的两个例子会更容易理解。

    值得一提的是,关于代码里的Reflect原本以为是这个 sec-reflect-object 里的方法,但可惜不是;

    然后猜测是Typescript的实现,翻了 Typescript/tsc.js 的代码(如果打不开链接就从 node_modules 下看吧),发现也不是;

    再去查 stackoverflow 的解释,是这样的 what-is-reflect-decorate-in-js-code-transpiled-from-ts

    大致说是ts希望把这个锅甩给ES来补,到时候 ts 的 else 里的代码便是 polyfill 了

    案例结合

    以下面的 decorator 和 class 作为例子解释

    // ts 代码
    function show(target: any) {
      console.log(target);
      target.prototype.showMe = (name: string) => {
        console.log("show me :", name);
      };
    }
    
    interface IShow {
      showMe?(name: string): any;
    }
    
    @show
    class Show implements IShow {
      showMe(name: string) {}
    }
    
    const shoow = new Show();
    shoow.showMe("ys");
    
    // 编译后的js
    // decorator ,简单的打印,并且修改方法
    function show(target) {
      console.log(target);
      target.prototype.showMe = function(name) {
        console.log("show me :", name);
      };
    }
    
    // class Shoow
    var Shoow = (function() {
      function Shoow() {}
      Shoow.prototype.showMe = function(name) {};
      // decorators 为[show],target 为 Shoow
      Shoow = __decorate([show], Shoow);
      return Shoow;
    })();
    
    var shooow = new Shoow();
    shooow.showMe("ys");
    
    // output : show me : ys

    理解一下执行的步骤:

    1. decorators = [show],target = Shoow;

    2. c = 2,r = target{Shoow},d = undefined;

    3. 不存在 Reflect,走循环,只循环一次;

    4. d = show,r = show(target{Shoow}),r 没返回结果,所以 r 还是 r , r = target{Shoow};

    5. return 结果: c = 2, 所以返回 false;

    6. 执行后的无返回值,但是在执行 show(target{Shoow}) 的时候将 showMe 方法改掉了,执行结果符合预期。

    一个不够?再来一个?这次在里面返回一个函数试试...

    // ts代码
    function logger1(config?) {
      return function(target, key: string, descriptor: PropertyDescriptor) {
        const _value = descriptor.value;
        if (typeof _value === "function") {
          descriptor.value = (...args) => {
            console.log(`logger1-begin : ${config.level}`);
            const res = _value.apply(target, args);
            console.log(`logger1-end`);
            return res;
          };
        }
        return descriptor;
      };
    }
    
    function logger2(config?) {
      return function(target, key: string, descriptor: PropertyDescriptor) {
        const _value = descriptor.value;
        if (typeof _value === "function") {
          descriptor.value = (...args) => {
            console.log(`logger2-begin : ${config.level}`);
            const res = _value.apply(target, args);
            console.log(`logger2-end`);
            return res;
          };
        }
        return descriptor;
      };
    }
    
    interface IShow {
      showMe?(name: string): any;
    }
    
    class Show implements IShow {
      @logger1({ level: "info" })
      @logger2({ level: "error" })
      showMe(name: string) {
        console.log("show me :", name);
      }
    }
    
    const shoow = new Show();
    shoow.showMe("ys");
    
    // output 这里手动加个缩进,这时候showMe方法已经经过多次包裹
    // logger1-begin : info
    //   logger2-begin : error
    //     show me : ys
    //   logger2-end
    // logger1-end

    再来看看执行步骤:

    1. decorators = [logger1, logger2],target = Shoow,key = "showMe",desc = null 注意,这里是为null,不是为undefined;

    2. c = 4,r = target{Shoow},d = undefined;

    3. 不存在 Reflect,走循环,只循环一次;

    4. 第一次循环取 d = logger1,r = logger1(target, key, r),因为 return 存在值,r = logger1 的返回函数,这时候 descriptor.value 被第一次重写;

    5. 第二次循环取 d = logger2,r = logger2(target, key, r),又因为 return 存在值,r = logger2 的返回函数,这时候 descriptor.value 被第二次重写;

    6. return 结果: 因为 c > 3,r 存在值,执行 Object.defineProperty(target, key, r)来重写对象属性并且返回 r (r为重写的结果);

    7. 经过 2 次重写 showMe 属性值,执行结果符合预期。

    欢乐

    装饰器给你带来什么欢乐?

    简单列几个最明显的优点,其他在运用中各自提取每日份的快乐去吧...

    1. 业务和功能之间的解耦(比如日志)

      // 日常
      dosomething(){
        consol.log('start')
        // some thing
        console.log('end')
      }
    
      // 使用装饰器
      @logger(logConfig?)
      dosomething();

    2. 代码结构清晰(特别针对多层HOC后的React组件)

      // 日常多层HOC
      class MyComponent extends Component{
        // ..
      }
      connect(mapFn)(
        MyHoc(someMapFn)(
          Form.create(fieldsMapFn)(MyComponent)
        )
      )
    
      // 使用装饰器
      @connect(mapFn)
      @MyHoc(someMapFn)
      @FormFields(fieldsMapFn)
      class MyComponent extends Component{
        // ..
      }
      export default MyComponent;

    最后

    注意编译

     tsc --target ES5 --experimentalDecorators

    或者 tsconfig.json 里加入

    {
        "compilerOptions": {
            "target": "ES5",
            "experimentalDecorators": true
        }
    } 

    顺便,AOP了解一下...

  • 相关阅读:
    python 字符串内建函数之开头与结尾判断
    python 字符串内建函数之查找、替换
    python 字符串内建函数之大小写
    python 字符串切片
    python for循环
    python if语句
    python input( )
    python 变量命名规则
    DllMain
    静态库lib和动态dll的区别及使用方法
  • 原文地址:https://www.cnblogs.com/ys-ys/p/10758450.html
Copyright © 2020-2023  润新知