Decorator & Reflect Metadata & InversifyJS

  • InversifyJS 比 SOLID(面向對象設計)中的 D(依賴反轉)好在哪裏?
  • 四種 Decorator
  • Reflect Metadata
  • InversifyJS

SOLID-D VS InversifyJS

SOLID-D

  • 高層模塊不該該依賴於低層模塊,他們都應該依賴於抽象接口
  • 抽象接口應該脫離具體實現,具體實現應該依賴於抽象接口
// 反例
class InventoryTracker {
  constructor(items) {
    this.items = items;

    // 依賴了一個具體實現
    this.requester = new InventoryRequester();
  }

  requestItems() {
    this.items.forEach((item) => {
      this.requester.requestItem(item);
    });
  }
}

class InventoryRequester {
  constructor() {
    this.REQ_METHODS = ['HTTP'];
  }

  requestItem(item) {
    // ...
  }
}

let inventoryTracker = new InventoryTracker(
  ['apples', 'bananas'],
);
inventoryTracker.requestItems();

// 正例
class InventoryTracker {
  constructor(items, requester) {
    this.items = items;
    this.requester = requester;
  }

  requestItems() {
    this.items.forEach((item) => {
      this.requester.requestItem(item);
    });
  }
}

class InventoryRequesterV1 {
  constructor() {
    this.REQ_METHODS = ['HTTP'];
  }

  requestItem(item) {
    // ...
  }
}

class InventoryRequesterV2 {
  constructor() {
    this.REQ_METHODS = ['WS'];
  }

  requestItem(item) {
    // ...
  }
}

// 依賴外置,經過參數注入依賴
// 咱們能夠輕鬆替換依賴項
let inventoryTracker = new InventoryTracker(
  ['apples', 'bananas'],
  new InventoryRequesterV2(),
);
inventoryTracker.requestItems();
複製代碼

InversifyJS

InversifyJS 一個特性是能夠利用 lazyInject 來實現無需把依賴放入 constructor 參數中,直接注入爲類屬性。node

@injectable()
export class Events {
  @lazy_inject(BINDING.Blink) protected Blink:DI.Blink;
  @lazy_inject(BINDING.touch_manager) protected touch_manager:DI.TouchManager;

  @lazy_inject(BINDING.WorkspaceSvg) protected WorkspaceSvg:DI.WorkspaceSvgClass;
}
複製代碼

下面將從 Decorator、Reflect-Metadata 解釋 InversifyJS 的實現原理。git

TS 中的 Decorator

四種 Decorator

Decorator 類型的定義:es6

  • ClassDecorator
  • MethodDecorator
  • PropertyDecorator
  • ParameterDecorator

__decorate

當咱們使用註解時,TypeScript 編譯器就會產生一個 __decorator 函數。github

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);
  }
};
複製代碼

Class Decorator

編譯結果

@logClass
class Person { 

  public name: string;
  public surname: string;

  constructor(name : string, surname : string) { 
    this.name = name;
    this.surname = surname;
  }
}

// 編譯結果:
// 使用了__decorate 的返回值來覆蓋原始構造函數
// 這就是類裝飾器必須返回構造函數的緣由
// 類裝飾器接受一個參數 target
var Person = (function () {
  function Person(name, surname) {
    this.name = name;
    this.surname = surname;
  }
  Person = __decorate(
    [logClass],          // decorators
    Person,              // target
  );
  return Person;
})();
複製代碼

logClass 實現

function logClass(target: any) {

  // 保存對原始構造函數的引用
  const original = target;

  // 生成類實例的工具函數
  function construct(constructor, args) {
    var c: any = function () {
      return constructor.apply(this, args);
    }
    c.prototype = constructor.prototype;
    return new c();
  }

  // 新的構造器行爲
  var f: any = function (...args) {
    console.log("New: " + original.name);
    return construct(original, args);
  }

  // 複製原型,所以 instanceof 仍然有效
  f.prototype = original.prototype;

  // 返回新構造函數(將覆蓋原始構造函數)
  return f;
}
複製代碼

Property Decorator

編譯結果

class Person {
  @logProperty
  public name: string;
  public surname: string;

  constructor(name: string, surname: string) {
    this.name = name;
    this.surname = surname;
  }
}

// 編譯結果:
// 屬性裝飾器只須要 2 個參數(原型和鍵)
// 屬性裝飾器沒有返回值
var Person = (function () {
  function Person(name, surname) {
    this.name = name;
    this.surname = surname;
  }
  __decorate(
    [logProperty],     // decorators
    Person.prototype,  // target
    'name',            // key
  );
  return Person;
})();

複製代碼

logProperty 實現

// 
function logProperty(target: any, key: string) {
  // 屬性值
  var _val = this[key];

  // 屬性 getter
  var getter = function () {
    console.log(`Get: ${key} => ${_val}`);
    return _val;
  };

  // 屬性 setter
  var setter = function (newVal) {
    console.log(`Set: ${key} => ${newVal}`);
    _val = newVal;
  };

  // 從類原型中刪除原始屬性
  // 若是屬性被成功刪除
  // Object.defineProperty() 方法用於使用原始屬性的名稱建立一個新的屬性
  if (delete this[key]) {

    // 使用 getter 和 setter 建立新屬性
    Object.defineProperty(target, key, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true
    });
  }
}
複製代碼

Method Decorator

編譯結果

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

// 編譯結果:
// 方法裝飾器須要 3 個參數(原型,鍵和屬性描述符)
var C = (function () {
  function C() {
  }
  C.prototype.foo = function (n) {
    return n * 2;
  };
  Object.defineProperty(
    __decorate(
      [log],                                              // decorators
      C.prototype,                                        // target
      "foo",                                              // key
      Object.getOwnPropertyDescriptor(C.prototype, "foo") // desc
    );
  return C;
})();
複製代碼

logMethod 實現

/** * * @param target 被註解的方法 * @param key 被註解方法的名字 * @param value * 屬性的描述,若是這個屬性不存在於對象上則爲 undefined * 能夠經過 Object.getOwnPropertyDescriptor() 函數得到 */
function logMethod(target: Function, key: string, value: any) {
  // target === C.prototype
  // key === "foo"
  // value === Object.getOwnPropertyDescriptor(C.prototype, "foo")
  return {
    value(...args: any[]) {
      // 將 foo 參數列表轉換爲字符串
      const a = args.map(a => JSON.stringify(a)).join();
      // 調用 foo() 並獲取其返回值
      const result = value.value.apply(this, args);
      // 將結果轉換爲字符串
      const r = JSON.stringify(result);
      // 在控制檯中顯示函數調用詳細信息
      console.log(`Call: ${key}(${a}) => ${r}`);
      // 返回調用 foo 的結果
      return result;
    }
  };
}
複製代碼

foo 註解以後也是正常執行,只是還會額外運行 log 的功能,這些功能是由 log 註解添加的。chrome

const c = new C();
const r = c.foo(23); // "Call: foo(23) => 46"
console.log(r);    // 46
複製代碼

Parameter Decorator

編譯結果

class Person {

  public name: string;
  public surname: string;

  constructor(name: string, surname: string) {
    this.name = name;
    this.surname = surname;
  }

  public saySomething(@logParameter something: string): string {
    return this.name + " " + this.surname + " says: " + something;
  }
}

// 編譯後代碼
Object.defineProperty(
  Person.prototype,
  "saySomething",
  __decorate(
    // 當 __decorate 調用 __param 時 ,它的返回將不會用於覆蓋該 saySomething 方法
    // 參數裝飾器不返回
    [__param(0, logParameter)], 
    Person.prototype, // 正在裝飾的類的原型
    "saySomething", // 包含要裝飾的參數的方法的名稱
    Object.getOwnPropertyDescriptor(Person.prototype, "saySomething") // 要裝飾的參數的索引
  ),
);
return Person;
複製代碼

__param.js 實現

// __param 函數由 TypeScript 編譯器生成
// 裝飾器包裝器 __param 用於在閉包中存儲參數的索引
// 索引只是參數列表中的位置
var __param = this.__param || function (index, decorator) {

  // 返回裝飾器函數(包裝器)
  return function (target, key) {

    //應用裝飾器(忽略返回)
    decorator(target, key, index);
  }
};
複製代碼

logParameter 實現

function logParameter(target: any, key: string, index: number) {
  var metadataKey = `log_${key}_parameters`;
  if (Array.isArray(target[metadataKey])) {
    target[metadataKey].push(index);
  }
  else {
    target[metadataKey] = [index];
  }
}
複製代碼

上面的參數裝飾器 metadataKey 向類原型添加了一個新的屬性(metadataKey)。新屬性是一個數組,包含要裝飾的參數的索引。咱們能夠將這個新屬性視爲元數據。express

參數裝飾器不該該修改構造函數,方法或屬性的行爲。參數裝飾器只應用於生成某種元數據json

裝飾工廠

裝飾器工廠是一個能夠接受任意數量參數的函數,而且必須返回一種類型的裝飾器。api

四種裝飾器

@logClass
class Person {

  @logProperty
  public name: string;

  public surname: string;

  constructor(name: string, surname: string) {
    this.name = name;
    this.surname = surname;
  }

  @logMethod
  public saySomething(@logParameter something: string): string {
    return this.name + " " + this.surname + " says: " + something;
  }
}
複製代碼

上面一段代碼應用了全部可用的裝飾器(類、方法、屬性和參數),若是咱們能夠在任何地方使用裝飾器而不用管類型更好。數組

@log
class Person {

  @log
  public name: string;

  public surname: string;

  constructor(name: string, surname: string) {
    this.name = name;
    this.surname = surname;
  }

  @log
  public saySomething(@log something: string): string {
    return this.name + " " + this.surname + " says: " + something;
  }
}
複製代碼

裝飾器工廠函數

function log(...args: any[]) {
  switch (args.length) {
    case 1:
      return logClass.apply(this, args);
    case 2:
      return logProperty.apply(this, args);
    case 3:
      if (typeof args[2] === "number") {
        return logParameter.apply(this, args);
      }
      return logMethod.apply(this, args);
    default:
      throw new Error("Decorators are not valid here!");
  }
}
複製代碼

可配置的裝飾器

@logClassWithArgs({ when : { name : "remo"} })
class Person { 
  public name: string;

  // ...
}
複製代碼
function logClassWithArgs(filter: Object) {
  return (target: Object) => {
    // 在這裏實現類裝飾器
    // 類裝飾師將有權訪問裝飾器參數(篩選器)
    // 由於它們存儲在閉包中
  }
}
複製代碼

Reflect-Metadata

反射

咱們的 JS 應用程序愈來愈大,咱們開始須要一些工具(IoC)和(運行時類型斷言)這樣的功能來管理這種日益增長的複雜性。瀏覽器

強大的反射 API 應該容許咱們在運行時檢查未知對象並找出有關它的全部內容。咱們應該可以找到像這樣的東西:

  • 實例的名稱
  • 實例的類型
  • 哪些接口由實例實現
  • 實例屬性的名稱和類型。
  • 實例的構造函數參數的名稱和類型

在 JS 中,咱們可使用 Object.getOwnPropertyDescriptor()Object.keys() 等函數來查找有關實例的一些信息,但咱們須要反射來實現更強大的開發工具。

元數據

  • 裝飾器經過反射來獲取類屬性上面的批註
  • 可是 JS 的裝飾器更多的是對函數或者屬性進行一些操做,好比修改他們的值,代理變量,自動綁定 this 等等功能
  • 沒法實現經過反射來獲取究竟有哪些裝飾器添加到這個類/方法上,這時候就須要 Reflect Metadata

概念

  • Relfect Metadata,能夠經過裝飾器來給類添加一些自定義的信息
  • 而後經過反射將這些信息提取出來
  • 也能夠經過反射來添加這些信息
@Reflect.metadata('inClass', 'A')
class Test {
  @Reflect.metadata('inMethod', 'B')
  public hello(): string {
    return 'hello world';
  }
}

console.log(Reflect.getMetadata('inClass', Test)); // 'A'
console.log(Reflect.getMetadata('inMethod', new Test(), 'hello')); // 'B'
複製代碼
  • Metadata Key {Any}: 元數據的 Key,簡寫 k。對於一個對象來講,他能夠有不少元數據,每個元數據都對應有一個 Key
    • 能夠對象上面設置一個叫作 name 的 Key 用來設置他的名字,用 created time 的 Key 來表示他建立的時間
    • 這個 Key 能夠是任意類型, 內部本質就是一個 Map 對象
  • Metadata Value {Any}: 簡寫 v。元數據的類型,任意類型都行
  • Target {Object}: 簡寫 o。表示要在這個對象上面添加元數據
  • Property {String|Symbol}: 簡寫 p。用於設置在那個屬性上面添加元數據。不只僅能夠在對象上面添加元數據,甚至還能夠在對象的屬性上面添加元數據。當給一個對象定義元數據的時候,默認指定了 undefined 做爲 Property

引入

安裝庫 reflect-metadata

import 'reflect-metadata';

// 定義對象或屬性的元數據
Reflect.defineMetadata(metadataKey, metadataValue, target);
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);

// 檢查對象或屬性的原型鏈上是否存在元數據鍵
let result = Reflect.hasMetadata(metadataKey, target);
let result = Reflect.hasMetadata(metadataKey, target, propertyKey);

// 檢查對象或屬性是否存在本身的元數據鍵
let result = Reflect.hasMetadata(metadataKey, target);
let result = Reflect.hasMetadata(metadataKey, target, propertyKey);

// 獲取對象或屬性原型鏈上元數據鍵的元數據值
let result = Reflect.getMetadata(metadataKey, target);
let result = Reflect.getMetadata(metadataKey, target, propertyKey);

// 獲取對象或屬性的本身的元數據鍵的元數據值
let result = Reflect.getOwnMetadata(metadataKey, target);
let result = Reflect.getOwnMetadata(metadataKey, target, propertyKey);

// 獲取對象或屬性原型鏈上的全部元數據鍵
let result = Reflect.getMetadataKeys(target);
let result = Reflect.getMetadataKeys(target, propertyKey);

// 獲取對象或屬性的全部本身的元數據鍵
let result = Reflect.getOwnMetadataKeys(target);
let result = Reflect.getOwnMetadataKeys(target, propertyKey);

// 從對象或屬性中刪除元數據
let result = Reflect.deleteMetadata(metadataKey, target);
let result = Reflect.deleteMetadata(metadataKey, target, propertyKey);

// 經過裝飾器將元數據應用於構造函數
@Reflect.metadata(metadataKey, metadataValue)
class C {
  // 經過裝飾器將元數據應用於方法(屬性)
  @Reflect.metadata(metadataKey, metadataValue)
  method() {
  }
}
複製代碼

在 tsconfig.json 配置:

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["es6", "dom"],
    "types": ["reflect-metadata"],
    "module": "commonjs",
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}
複製代碼

用途

獲取屬性類型元數據

function logType(target: any, key: string) {
  var t = Reflect.getMetadata("design:type", target, key);
  console.log(`${key} type: ${t.name}`);
}
複製代碼
class Demo { 
  // 應用屬性裝飾器
  // 會打印出 attr1 type: String
  @logType 
  public attr1 : string;
}
複製代碼

獲取參數類型元數據

function logParamTypes(target: any, key: string) {
  var types = Reflect.getMetadata("design:paramtypes", target, key);
  var s = types.map(a => a.name).join();
  console.log(`${key} param types: ${s}`);
}
複製代碼
class Foo {}
interface IFoo {}

class Demo {
  // 應用參數裝飾器
  // 打印出:doSomething param types: String, Number, Foo, Object, Object, Function
  @logParameters 
  doSomething(
    param1: string,
    param2: number,
    param3: Foo,
    param4: { test: string },
    param5: IFoo,
    param6: (a: number) => void
  ): number {
    return 1;
  }
}
複製代碼

獲取返回類型元數據

咱們還可使用 "design:returntype" 元數據鍵獲取有關方法返回類型的信息:

Reflect.getMetadata("design:returntype", target, key);
複製代碼

基本類型序列化規則

  • number 序列化爲 Number
  • string 序列化爲 String
  • boolean 序列化爲 Boolean
  • any 序列化爲 Object
  • void 序列化爲 undefined
  • Array 序列化爲 Array
  • Tuple序列化爲 Array
  • class 序列化爲類構造函數
  • Enum 序列化爲 Number
  • 若是至少有一個簽名,則序列化爲 Function
  • 不然序列化爲 Object(包括接口)

實現原理

全部的元數據都是存在於對象下面的 [[Metadata]] 屬性下面,可是不是經過 Symbol 實現的:

@Reflect.metadata('name', 'A')
class A {}

Object.getOwnPropertySymbols(A) // []
複製代碼

內部的數據結構:

WeakMap<any, Map<any, Map<any, any>>>

// 調用的角度
// 先根據對象獲取,而後在根據屬性,最後根據元數據的 Key 獲取最終要的數據
weakMap.get(o).get(p).get(k)
複製代碼

例子

Controller 與 Get

// app.js
@Controller('/test')
class SomeClass {
  @Get('/a')
  someGetMethod() {
    return 'hello world';
  }

  @Post('/b')
  somePostMethod() {}
}
複製代碼
// Controller.js
const METHOD_METADATA = 'method'const PATH_METADATA = 'path'const Controller = (path: string): ClassDecorator => {
  return target => {
    Reflect.defineMetadata(PATH_METADATA, path, target);
  }
}

const createMappingDecorator = (method: string) => (path: string): MethodDecorator => {
  return (target, key, descriptor) => {
    Reflect.defineMetadata(PATH_METADATA, path, descriptor.value);
    Reflect.defineMetadata(METHOD_METADATA, method, descriptor.value);
  }
}

const Get = createMappingDecorator('GET');
const Post = createMappingDecorator('POST');
複製代碼
// mapRoute.js
function mapRoute(instance: Object) {
  const prototype = Object.getPrototypeOf(instance);

  // 篩選出類的 methodName
  const methodsNames = Object
    .getOwnPropertyNames(prototype)
    .filter(item => !isConstructor(item) && isFunction(prototype[item]));
  return methodsNames.map(methodName => {
    const fn = prototype[methodName];

    // 取出定義的 metadata
    const route = Reflect.getMetadata(PATH_METADATA, fn);
    const method = Reflect.getMetadata(METHOD_METADATA, fn);
    return {
      route,
      method,
      fn,
      methodName
    }
  })
};
複製代碼
Reflect.getMetadata(PATH_METADATA, SomeClass); // '/test'

mapRoute(new SomeClass());

/** * [{ * route: '/a', * method: 'GET', * fn: someGetMethod() { ... }, * methodName: 'someGetMethod' * },{ * route: '/b', * method: 'POST', * fn: somePostMethod() { ... }, * methodName: 'somePostMethod' * }] * */
複製代碼

InversifyJS

體系結構概述

InversifyJS 在解析依賴關係以前執行:

  • 註解(Annotation)
  • 規劃(Planning)
  • 中間件(Middleware)(可選)
  • 分解(Resolution)
  • 激活(Activation)(可選)

註解

註釋:一種將元數據添加到類聲明以供依賴注入或編譯指令使用的方法。

  • 註釋階段讀取裝飾器生成的元數據,並將其轉換爲請求類和目標類的一系列實例
  • 該請求類和目標類的實例將用於在規劃階段生成解決計劃

規劃

  • 咱們開始新的解析,這意味着容器將建立新的解析上下文。解析上下文包含對容器的引用和對 Plan 的引用
  • Plan 由 Planner 類的實例生成。該 Plan 包含對上下文的引用和對(根)請求的引用。請求表示將被注入目標的依賴
var obj = container.get<SomeType>("SomeType");

@injectable()
class FooBar implements FooBarInterface {
  public foo : FooInterface;
  public bar : BarInterface;
  public log() {
    console.log("foobar");
  }
  constructor(
    @inject("FooInterface") foo : FooInterface, 
    @inject("BarInterface") bar : BarInterface
  ) {
    this.foo = foo;
    this.bar = bar;
  }
}

var foobar = container.get<FooBarInterface>("FooBarInterface");
複製代碼

前面的代碼片斷將生成新的上下文和新的 Plan。該 Plan 將包含一個具備 null target 和兩個子 requests 的RootRequest:

  • 第一個子 request 表示對 fooInterface 的依賴,其目標是名爲 Foo 的構造函數參數
  • 第二個子 request 表示對 barInterface 的依賴,其目標是名爲 Bar 的構造函數參數

中間件

若是配置了一些中間件,它將在分解階段開始以前執行。中間件能夠用來開發一些瀏覽器擴展,這將容許咱們使用一些數據可視化工具來顯示解決方案,好比 D3.js。這種工具將幫助開發人員在開發過程當中發現問題。

分解階段

該計劃被傳遞給解析器類的實例。解析器將繼續解析請求樹中的每一個依賴項,從葉開始,以根請求結束。解析過程能夠同步或異步執行,這有助於提升性能。

激活

激活在依賴關係解決後進行。就在它被添加到緩存(若是是單例)並被注入以前。能夠添加一個事件處理程序,在激活完成以前調用它。這個特性容許開發人員作一些事情,好比注入代理來攔截對該對象的屬性或方法的全部調用。

控制依賴項的範圍

有許多可用的綁定:

interface BindingToSyntax<T> {
  to(constructor: { new(...args: any[]): T; }): BindingInWhenOnSyntax<T>;
  toSelf(): BindingInWhenOnSyntax<T>;
  toConstantValue(value: T): BindingWhenOnSyntax<T>;
  toDynamicValue(func: (context: Context) => T): BindingWhenOnSyntax<T>;
  toConstructor<T2>(constructor: Newable<T2>): BindingWhenOnSyntax<T>;
  toFactory<T2>(factory: FactoryCreator<T2>): BindingWhenOnSyntax<T>;
  toFunction(func: T): BindingWhenOnSyntax<T>;
  toAutoFactory<T2>(serviceIdentifier: ServiceIdentifier<T2>): BindingWhenOnSyntax<T>;
  toProvider<T2>(provider: ProviderCreator<T2>): BindingWhenOnSyntax<T>;
}
複製代碼

就做用域的行爲方式而言,咱們能夠將這些類型的綁定分爲兩個主要組:

  • 將注入 object
  • 講注入 function

object

interface BindingToSyntax<T> {
  to(constructor: { new(...args: any[]): T; }): BindingInWhenOnSyntax<T>;
  toSelf(): BindingInWhenOnSyntax<T>;
  toConstantValue(value: T): BindingWhenOnSyntax<T>;
  toDynamicValue(func: (context: Context) => T): BindingInWhenOnSyntax<T>;
}
複製代碼

默認狀況下使用 inTransientScope,咱們能夠選擇這種類型綁定的範圍,但 toConstantValue 除外,它將始終使用 inSingletonScope

當咱們第一次調用 container.get 時,咱們使用 totoSelftoDynamicValue,InversifyJS 容器將嘗試使用構造函數或動態值工廠生成對象實例或值。若是範圍設置爲 InSingleTonScope,則緩存該值。第二次咱們調用container.get 來獲取相同的資源 ID,若是選擇了 inSingletonScope,InversifyJS 將嘗試從緩存中獲取值。

function

interface BindingToSyntax<T> {
  toConstructor<T2>(constructor: Newable<T2>): BindingWhenOnSyntax<T>;
  toFactory<T2>(factory: FactoryCreator<T2>): BindingWhenOnSyntax<T>;
  toFunction(func: T): BindingWhenOnSyntax<T>;
  toAutoFactory<T2>(serviceIdentifier: ServiceIdentifier<T2>): BindingWhenOnSyntax<T>;
  toProvider<T2>(provider: ProviderCreator<T2>): BindingWhenOnSyntax<T>;
}
複製代碼

咱們不能選擇這種綁定的範圍,由於要注入的值(工廠函數)老是單例的。然而,工廠內部實現可能返回也可能不返回單例。如下綁定將注入一個工廠,該工廠將始終是單例。

container.bind<interfaces.Factory<Katana>>("Factory<Katana>").toAutoFactory<Katana>("Katana");
複製代碼

工廠返回的值多是單一值,也可能不是單一值:

container.bind<Katana>("Katana").to(Katana).inTransientScope();
// or
container.bind<Katana>("Katana").to(Katana).inSingletonScope();
複製代碼

容器快照

聲明容器快照是一項幫助您輕鬆編寫單元測試的功能:

import { expect } from "chai";
import * as sinon from "sinon";

// application container is shared by all unit tests
import container from "../../src/ioc/container";

describe("Ninja", () => {

  beforeEach(() => {

    // create a snapshot so each unit test can modify 
    // it without breaking other unit tests
    container.snapshot();

  });

  afterEach(() => {

    // Restore to last snapshot so each unit test 
    // takes a clean copy of the application container
    container.restore();

  });

  // each test is executed with a snapshot of the container

  it("Ninja can fight", () => {

    let katanaMock = {
      hit: () => { return "hit with mock"; }
    };

    container.unbind("Katana");
    container.bind<Something>("Katana").toConstantValue(katanaMock);
    let ninja = container.get<Ninja>("Ninja");
    expect(ninja.fight()).eql("hit with mock");

  });

  it("Ninja can sneak", () => {

    let shurikenMock = {
      throw: () => { return "hit with mock"; }
    };

    container.unbind("Shuriken");
    container.bind<Something>("Shuriken").toConstantValue(shurikenMock);
    let ninja = container.get<Ninja>("Shuriken");
    expect(ninja.sneak()).eql("hit with mock");

  });

});
複製代碼

容器的 API

github.com/inversify/I…

Container Options

defaultScope

默認範圍是 transient,能夠在聲明綁定時更改類型的範圍:

container.bind<Warrior>(TYPES.Warrior).to(Ninja).inTransientScope();
container.bind<Warrior>(TYPES.Warrior).to(Ninja).inSingletonScope();
複製代碼

使用容器選項更改應用程序級別使用的默認範圍:

let container = new Container({ defaultScope: "Singleton" });
複製代碼

autoBindInjectable

你能夠用這個激活 @injectable() 裝飾類的自動綁定:

let container = new Container({ autoBindInjectable: true });
container.isBound(Ninja);          // returns false
container.get(Ninja);              // returns a Ninja
container.isBound(Ninja);          // returns true
複製代碼

手動定義的綁定優先:

let container = new Container({ autoBindInjectable: true });
container.bind(Ninja).to(Samurai);
container.get(Ninja);              // returns a Samurai
複製代碼

Container.merge

將兩個容器合併爲一個。

container.getNamed

命名綁定。

container.getTagged

標記綁定。

container.getAll

獲取給定標識符的全部可用綁定。

container.rebind

可使用 rebind 方法替換給定 serviceIdentifier 的全部現有綁定。該函數返回 BindingTexture 的實例,該實例容許建立替換綁定。

let TYPES = {
  someType: "someType"
};

let container = new Container();
container.bind<number>(TYPES.someType).toConstantValue(1);
container.bind<number>(TYPES.someType).toConstantValue(2);

let values1 = container.getAll(TYPES.someType);
expect(values1[0]).to.eq(1);
expect(values1[1]).to.eq(2);

container.rebind<number>(TYPES.someType).toConstantValue(3);
let values2 = container.getAll(TYPES.someType);
expect(values2[0]).to.eq(3);
expect(values2[1]).to.eq(undefined);
複製代碼

container.resolve

Resolve 就像 container.get<T>(serviceIdentifier: ServiceIdentifier<T>),可是它容許用戶建立一個實例,即便沒有聲明綁定。

請注意,它只容許跳過聲明依賴關係圖中根元素的綁定(組合根)。全部的子依賴項都須要聲明綁定。

類 lazyInject

github.com/inversify/I…

import "reflect-metadata";
import { Container, injectable, inject } from "inversify";
import getDecorators from "inversify-inject-decorators";

const container = new Container();
const { lazyInject } = getDecorators(container);

const TYPE = {
  Dom: Symbol.for("Dom"),
  DomUi: Symbol.for("DomUi")
};

@injectable()
class DomUi {
  public dom: Dom;
  public name: string;
  constructor(
    @inject(TYPE.Dom) dom: Dom
  ) {
    this.dom = dom;
    this.name = "DomUi";
  }
}

@injectable()
class Dom {
  public name: string;
  @lazyInject(TYPE.DomUi) public domUi: DomUi;
  public constructor() {
    this.name = "Dom";
  }
}

@injectable()
class Test {
  public dom: Dom;
  constructor(
    @inject(TYPE.Dom) dom: Dom
  ) {
    this.dom = dom;
  }
}

container.bind<Dom>(TYPE.Dom).to(Dom).inSingletonScope();
container.bind<DomUi>(TYPE.DomUi).to(DomUi).inSingletonScope();

const test = container.resolve(Test); // Works!
複製代碼

生態系統

工具:

  • inversify-binding-decorators - 容許開發人員使用裝飾器聲明延遲求值屬性注入。
  • inversify-inject-decorators - 容許開發人員使用裝飾器聲明InversifyJS綁定。
  • inversify-express-utils - 用 Express 開發快速應用程序的一些實用程序。
  • inversify-restify-utils - 使用 Restify 開發快速應用程序的一些實用程序。
  • inversify-vanillajs-helpers - 使用 VanillaJS 或 Babel 開發 InversifyJS 應用程序的一些幫助程序。
  • inversify-tracer - 容許開發人員跟蹤 InversifyJS 建立的對象方法的工具。
  • inversify-components - InversifyJS 之上的層,使您能夠將應用程序拆分爲多個組件。
  • inversify-token - InversifyJS 的基於令牌的層,爲 TypeScript 中的注入啓用強類型安全保證。

中間件:

  • nversify-logger-middleware - InversifyJS的控制檯記錄器中間件。

開發工具

  • inversify-devtools - 支持瀏覽器擴展的 Web 應用程序。
  • inversify-chrome-devtools - 一個 chrome 擴展,旨在幫助開發人員使用 InversifyJS。

文章參考:

相關文章
相關標籤/搜索