淺入淺出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();
  1. 代碼結構清晰(特別針對多層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;

最後

AOP,瞭解一下

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章