一、背景

在 Angular 技术栈,Service 需要通过 Angular 提供的依赖注入能力进行使用。然而这个能力容易被滥用,导致了一些场景下的循环依赖,而且这个循环依赖还不是很好解开。鉴于有迁移 React 的打算,希望能通过一些规范来避免在 React 中也出现循环依赖问题,于是有了这篇文档。

二、选型

Angular 的依赖注入能力是自身框架提供的能力,React / Vue 等框架没有这样的能力。如果想用这样的能力,所以需要借助其他的工具库来实现。这里推荐使用微软开源的依赖注入库:tsyringe
理由有两个:1、大厂出品;2、兄弟团队已经使用了较长一段时间,并没有什么问题。此外,该库是纯 TS 的,跟 UI 框架毫无关系,这和 Angular 的依赖注入依赖 Angular 框架本身有很大的优势:易于迁移(比如React、Vue可以共享),其提供的主要API见下表:

类型 API 作用
Decorators injectable 声明一个可注入的类
singleton 同上,只不过是单例
inject 参数注入
injectAll 数组参数注入
scoped 类注入器,可以区分 scope
Containers resolve 获取一个类对应的实例
register 手动注册 Provider,useClass,useValue之类
beforeResolution 解析之前钩子
afterResolution 解析之后钩子
createChildContainer 创建子容器
clearInstances 销毁实例,不销毁注册
reset 全部销毁,需要重新注册
Circular dependencies delay 解决依赖注入

三、规范

  • 区分 BaseService 和 BusinessService

1、BaseService 不允许依赖任何 BusinessService,尽量也不要依赖其它 BaseService
2、BusinessService 可以依赖 BaseService 和 BusinessService
3、BaseService 都为单例,BusinessService 视情况而定
4、Service 都用 tsyringe 的 Decorator 来声明
5、Service 以 Pascal 风格命名,以 Service 为后缀结束,如 ClogBaseService
6、BusinessService 之间尽量不要互相依赖

  • Service 写法 ```javascript // 注册 import {singleton} from ‘tsyringe’;

@singleton() export class FooService { constructor() {} }

// 使用场景1:在其它 Service 里使用 import {singleton} from ‘tsyringe’; import {FooService} from ‘./FooService’;

@singleton() export class BarService{ constructor(public foo: FooService) {} }

// 使用场景2:在非 Service 里使用 import {container} from ‘tsyringe’; import {FooService} from ‘./FooService;

const foo = container.resolve(FooService)

  1. - 注册常量
  2. ```javascript
  3. // 注册
  4. const str = "test";
  5. container.register("SpecialString", {useValue: str});
  6. // 使用
  7. @singleton()
  8. class Foo {
  9. private str: string;
  10. constructor(@inject("SpecialString") value: string) {
  11. this.str = value;
  12. }
  13. }
  • 循环依赖解法

如果出现了循环依赖,应该优先重构代码,万一有某种原因无法重构或成本太高,可考虑如下方式:

  1. // before
  2. @injectable()
  3. export class Foo {
  4. constructor(public bar: Bar) {}
  5. }
  6. @injectable()
  7. export class Bar {
  8. constructor(public foo: Foo) {}
  9. }
  10. // after
  11. @injectable()
  12. export class Foo {
  13. constructor(@inject(delay(() => Bar)) public bar: Bar) {}
  14. }
  15. @injectable()
  16. export class Bar {
  17. constructor(@inject(delay(() => Foo)) public foo: Foo) {}
  18. }
  19. // construction of foo is possible
  20. const foo = container.resolve(Foo);
  21. // property bar will hold a proxy that looks and acts as a real Bar instance.
  22. foo.bar instanceof Bar; // true
  • UT 相关 ```javascript @singleton() class Foo {}

beforeEach(() => { container.clearInstances(); });

test(“something”, () => { container.resolve(Foo); // will be a new singleton instance in every test }); ```

  • 门面模式解决 Service 互相依赖问题

所有 service、component 都不互相引用,所需依赖统一从 ServiceManager 取:
image.png
image.png