十幾行代碼實現一個ts依賴注入

項目地址

靈感來自angular和我本身寫的框架indivreact

感謝大佬們star使用git

githubgithub

關於依賴注入

IOC(Inversion of Control)即控制反轉,DI(Dependency Injection)即依賴注入。typescript

假設咱們有一個類Human,要實例一個Human,咱們須要實例一個類Clothes。而實例化衣服Clothes,咱們又須要實例化布Cloth,實例化鈕釦等等。編程

當需求達到必定複雜的程度時,咱們不能爲了一我的穿衣服去從布從鈕釦開始從頭實現,最好能把全部的需求放到一個工廠或者是倉庫,咱們須要什麼直接從工廠的倉庫裏面直接拿。後端

這個時候就須要依賴注入了,咱們實現一個IOC容器(倉庫),而後須要衣服就從倉庫裏面直接拿實例好的衣服給人做爲屬性穿上去。數組

IOC是一種很好的解耦合思想,在開發中,IoC意味着你設計好的對象交給容器控制,而不是使用傳統的方式,在對象內部直接控制。在軟件開發中有很好的做用,不只被應用在JavaEE裏,在其它語言裏一樣適用。安全

其實總結起來大概就是你須要我,工廠就把我打扮好派我去找你。數據結構

實現一個IOC容器

由於key類型不必定是字符型,並且數組遍歷比較浪費性能,所以不選擇Object和Array而選擇Map做爲容器的數據結構。框架

由於目標是實現一個能夠懶漢模式的IOC容器,因此能夠在類Injector裏寫了兩個私有類,一個存token和依賴,一個存token和實例化的依賴實例。

在須要某個依賴實例的時候,先去實例的Map中根據token查找出對應的token的實例,若是沒有就從存放依賴類的Map中拿出來實例化以後再放入存放實例的Map。

export class Injector {
  private readonly providerMap: Map<any, any> = new Map();
  private readonly instanceMap: Map<any, any> = new Map();
  public setProvider(key: any, value: any): void {
    if (!this.providerMap.has(key)) this.providerMap.set(key, value);
  }
  public getProvider(key: any): any {
    return this.providerMap.get(key);
  }
  public setInstance(key: any, value: any): void {
    if (!this.instanceMap.has(key)) this.instanceMap.set(key, value);
  }
  public getInstance(key: any): any {
    if (this.instanceMap.has(key)) return this.instanceMap.get(key);
    return null;
  }
  public setValue(key: any, value: any): void {
    if (!this.instanceMap.has(key)) this.instanceMap.set(key, value);
  }
}
複製代碼

如今new一下,這個容器就建立出來了。

export const rootInjector = new Injector();
複製代碼

實現服務

偷偷借走了ng的Injectable,經過類裝飾器把類存入容器。

export function Injectable(): (_constructor: any) => any {
  return function (_constructor: any): any {
      rootInjector.setProvider(_constructor, _constructor);
      return _constructor;
  };
}
複製代碼

把類做爲token,把該類存入provider容器,提供給須要依賴的類。

實現基於註解的屬性注入

之因此不實現構造注入,setter注入是由於像ng和react這類框架會對直接對構造函數進行注入或是限制構造函數的參數,爲了儘可能跨框架跨先後端使用,因此仍是用裝飾器對屬性注入儘可能減小侵入性。

屬性裝飾器和反射能幫咱們實現這一功能。

export function Inject(): (_constructor: any, propertyName: string) => any {
  return function (_constructor: any, propertyName: string): any {
    const  propertyType: any = Reflect.getMetadata('design:type', _constructor, propertyName);
    const injector: Injector = rootInjector;

    let providerInsntance = injector.getInstance(propertyType);
    if (!providerInsntance) {
        injector.getProvider(propertyType);
        providerInsntance = new providerClass();
        injector.setInstance(key, providerInsntance);
    }
    _constructor[propertyName] = providerInsntance;

    return (_constructor as any)[propertyName];
  };
}
複製代碼

使用Reflect的元數據 Reflect.getMetadata('design:type') 獲取屬性的類型,並做爲token去 injector.getInstance 查詢對應的實例,若是有則直接將屬性映射爲查找到的實例。這樣就保證咱們每次使用裝飾器的屬性都會得到單例。

若是沒有查詢到則去另一個Map中拿出依賴並實例化存入實例的Map。由於js單線程懶漢模式不存在線程安全這麼一說,因此沒有選擇初始化時就把所有的依賴實例化。

demo

import { Inject, Injectable } from '../injector';

@Injectable()
class Cloth {
    public name: string = '麻布';
}

@Injectable()
class Clothes {
  @Inject() public cloth: Cloth;
}

class Human {
  @Inject() public clothes: Clothes;
}

const pepe = new Human();
console.log(pepe);
// {
// clothes: {
// cloth: {
// name: '麻布'
// }
// }
//}
複製代碼

最後咱們能夠直接new pepe,不須要關心pepe穿的衣服 Clothes 和衣服須要的布料 Cloth

最後推薦結合rxjs作響應式編程。ng大法好!

相關文章
相關標籤/搜索