简单实现类似Angular的依赖注入

框架

浏览数:160

2019-9-7

AD:资源代下载服务

依赖注入是一个很重要的设计模式。 它使用得非常广泛,以至于几乎每个人都把它简称为 DI 。

Angular 有自己的依赖注入框架,离开它,你几乎没办法构建出 Angular 应用。

下面介绍一种简单的(只有100行左右代码)实现类似Angular依赖注入的方式,先看一个例子

// a.service.ts
@Injectable()
export class AService {

  constructor() {

  }

  public doSomething() {
    console.log('this is AService::doSomething');
  }

}

// b.service.ts
@Injectable()
export class BService {

  constructor(private readonly a: AService) {

  }

  public doSomething() {
    this.a.doSomething();
    console.log('this is BService::doSomething');
  }

}

// some.module.ts
@Module({
  providers: [
    AService,
    BService,
  ],
})
export class SomeModule {

}

在上面的例子中,我们创建了两个Service,其中BService依赖于AService,那么BService可以在其构造函数中声明其依赖,我们需要一种方法去自动将AService的实例注入到BService的私有只读变量a中,接下来介绍实现的步骤。

首先我们应该对Typescript进行配置,使其支持Javascript的装饰器(或者说是注解),下面是我的配置文件:

{
  "compilerOptions": {
    "lib": [
      "dom",
      "es2015"
    ],
    "target": "es5",
    "module": "commonjs",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

其中,experimentalDecorators表示为ES装饰器启用实验支持emitDecoratorMetadata表示在源代码中为装饰声明产生类型的元数据,配置好了这两项之后,我们才能使用Typescript的装饰器,接下来我们需要安装依赖reflect-metadata,用于读取和设置元数据。

@Injectable的实现

@Injectable是一个装饰器,它标识被装饰的类是一个Provider,它的声明方式如下

export function Injectable(): ClassDecorator {
  return (target) => {

  };
}

我们在此装饰器中什么都不做,他只起到一个标识的作用。

@Module的实现

@Module是一个装饰器,它标识被装饰的类是一个Module,它的声明方式如下

const DI_IMPORTS_SYMBOL = Symbol('di:imports')
const DI_PROVIDERS_SYMBOL = Symbol('di:providers')

export function Module(options: { imports?: Array<any>, providers?: Array<any> }): ClassDecorator {
  return (target) => {
    Reflect.defineMetadata(DI_IMPORTS_SYMBOL, new Set(options.imports || []), target);
    Reflect.defineMetadata(DI_PROVIDERS_SYMBOL, new Set(options.providers || []), target);
  }
}

我们使用Set来存储一个Module作用域中它所声明的Providers和它所引入的其他模块。

Factory的实现

我们希望达到的目的是,在使用时可以通过Factory.create(SomeModule)来获取一个Module的实例,然后通过Module实例来获取一个Provider,例如Factory.create(SomeModule).get(BService).doSomething(),此时应该输出

Factory.create(SomeModule).get(BService).doSomething();
// this is AService::doSomething
// this is BService::doSomething

Talk is cheap. Show me the code:

export namespace Factory {

  export function create(module: Type) {
    const imports: Set<Type> = Reflect.getMetadata(DI_IMPORTS_SYMBOL, module);
    const providers: Set<any> = Reflect.getMetadata(DI_PROVIDERS_SYMBOL, module);
    const providersMap = new Map();

    const importModules = Array.from(imports).map((importModule) => {
      let moduleInstance: ModuleInstance = moduleInstances.get(importModule);
      if(!moduleInstance) {
        moduleInstance = create(importModule);
        moduleInstances.set(importModule, moduleInstance);
      }
      return moduleInstance;
    });
    const moduleInstance = new ModuleInstance(importModules, providersMap);

    providers.forEach(provider => {
      createProvider(provider, providers, moduleInstance);
    });
    return moduleInstance;
  }

  function createProvider(provider: any, providers: Set<any>, moduleInstance: ModuleInstance) {
    let providerInstance = moduleInstance.providers.get(provider);

    if(providerInstance) {
      return providerInstance;
    }

    const deps: Array<any> = Reflect.getMetadata('design:paramtypes', provider);
    if(!deps) {
      throw new Error(`No provider named ${ provider.name }, do yout add @Injectable() to this provider?`);
    }

    const args = deps.map(dep => {
      let depInstance = moduleInstance.providers.get(dep);
      if(!depInstance) {
        if(providers.has(dep)) {
          depInstance = createProvider(dep, providers, moduleInstance);
        } else {
          moduleInstance.imports.some(imp => {
            depInstance = createProvider(dep, new Set(), imp);
            return !!depInstance;
          });
        }
      }
      if(!depInstance) {
        throw new Error(`can not found provider ${ dep.name }`);
      }
      return depInstance;
    });
    providerInstance = new provider(...args);
    moduleInstance.providers.set(provider, providerInstance);
    return providerInstance;
  }

  export class ModuleInstance {

    constructor(
        public imports: Array<ModuleInstance>,
        public providers: Map<any, any>) {

    }

    get<T>(provider: Type<T>) {
      let instance: T = this.providers.get(provider);
      if(!instance) {
        this.imports.some(imp => {
          instance = imp.get(provider);
          return !!instance;
        });
      }
      if(!instance) {
        throw new Error(`No provider named: ${ provider.name }`);
      }
      return instance;
    }
  }

}

以上就是整个依赖注入的实现了,感兴趣的朋友可以到我的Github上面查看源代码,核心文件就是lib/di.ts,地址是
https://github.com/hungtcs/li…

作者:鸿则