天天看點

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()

    ,觀察運作結果

繼續閱讀