reflect metadata & dependency injection

本系列文章是本人學習相關知識時所積累的筆記,以記錄自己的學習歷程,也爲了方便回顧知識;故文章內容較爲隨意簡練,抱着學習目的來的同學務必轉移他處,以免我誤人子弟~

Decorators & metadata reflection in TypeScript: From Novice to Expert (Part I)
從 JavaScript 到 TypeScript 4 - 裝飾器和反射

關於裝飾器函數需不需要返回值的問題

declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;

declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;

declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;

declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void;

  • 類裝飾器接收1個參數(當前類的構造函數F),通過修改該構造函數並返回可達到修改類的效果(也可不返回)
  • 屬性裝飾器接收2個參數(當前類的原型對象和屬性名),無需返回值

    靜態屬性接收當前 類的構造函數 和 屬性名

  • 方法裝飾器接收3個參數(當前類的原型對象、方法名和方法描述對象),通過修改方法描述對象並返回達到修改方法的效果(也可不返回)

    靜態方法接收當前 類的構造函數 、屬性名 和 方法描述對象

  • 參數裝飾器接收3個參數(當前類的原型對象、方法名和參數所在方法的索引),無需返回值

查看 MethodDecorator 的ts解析代碼

ts代碼:

class C {
    @log
    foo(n: number) {
        return n * 2;
    }
}

function log(target: Object, key: string, descriptor: any) {
    ...
}

解析後的js代碼:

var C = (function () {
    function C() {
    }
    C.prototype.foo = function (n) {
        return n * 2;
    };
    Object.defineProperty(C.prototype, "foo",
        __decorate(
	        [log], 
	        C.prototype, 
	        "foo", 
	        Object.getOwnPropertyDescriptor(C.prototype, "foo")
        ));
    return C;
})();

var __decorate = this.__decorate || function (decorators, target, key, desc) {
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
    return Reflect.decorate(decorators, target, key, desc);
  }
  switch (arguments.length) {
    case 2: 
      return decorators.reduceRight(function(o, d) { 
        return (d && d(o)) || o; 
      }, target);
    case 3: 
      return decorators.reduceRight(function(o, d) { 
        return (d && d(target, key)), void 0; 
      }, void 0);
    case 4: 
      return decorators.reduceRight(function(o, d) { 
        return (d && d(target, key, o)) || o; 
      }, desc);
  }
};

爲什麼是 reduceRight
因爲typescript的裝飾器是 先進後出 的原則;假設有3個方法裝飾器 a1 a2 a3,則 decorators 爲[a1 ,a2 ,a3],a1最先進入,a3最後進入;但當方法被調用時,裝飾器的執行順序是從右到左,即a3 a2 a1 的順序

如果是 MethodDecorator ,即 case 4,則 __decorate最終會返回一個方法描述對象,如果某個方法裝飾器返回方法描述對象desc,則 __decorate返回desc,否則放回方法原生的方法描述對象;先執行的裝飾器所返回的方法描述對象會被後面返回的取代

Tips

Typescript 中,Reflect.getMetadata 的使用有很多規矩需要注意:

import "reflect-metadata"

type Constructor<T = any> = new (...args: any[]) => T;

const ClassDec = (): ClassDecorator => {
  return (target) => {
      return;
  }
};
const PropertyDec = (): PropertyDecorator => {
  return (target, key) => {
      return;
  }
};
const MethodDec = (): MethodDecorator => {
  return (target, key, desc) => {
      return;
  }
};

class OtherService {
  constructor(){}
  a=0;
}
@ClassDec()
class TestService {
  constructor(public readonly otherService: OtherService) { };
  @PropertyDec()
  public name: string;
  @MethodDec()
  public handle(event: number) {
      console.log(this.otherService.a);
   }
}

const Factory = <T>(target: Constructor<T>): T => {
  // 獲取所有注入的服務
  console.log(Reflect.getMetadata('design:paramtypes', target));
  console.log(Reflect.getMetadata('design:type', target))
  console.log(Reflect.getMetadata('design:returntype', target));

  console.log(Reflect.getMetadata('design:paramtypes', target.prototype, "handle"));
  console.log(Reflect.getMetadata('design:type', target.prototype, "handle"))
  console.log(Reflect.getMetadata('design:returntype', target.prototype, "handle"));

  console.log(Reflect.getMetadata('design:paramtypes', target.prototype, "name"))
  console.log(Reflect.getMetadata('design:returntype', target.prototype, "name"));
  console.log(Reflect.getMetadata('design:type', target.prototype, "name"))
  return new target();
};

Factory(TestService);


  1. 類不支持提升,OtherService 必須在使用前聲明(寫在TestService 前面),否則 Reflect.getMetadata('design:paramtypes', target) 不起作用

    嘗試將OtherService放到TestService後面,觀察運行結果

  2. 要想在非裝飾器函數中取得元數據,需要對應的類(或屬性或方法)有一個裝飾器(此裝飾器可以不起其他作用)

    嘗試註釋掉 @ClassDec(), @PropertyDec(), @MethodDec(),觀察運行結果

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