一、背景
在 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)
- 注册常量```javascript// 注册const str = "test";container.register("SpecialString", {useValue: str});// 使用@singleton()class Foo {private str: string;constructor(@inject("SpecialString") value: string) {this.str = value;}}
- 循环依赖解法
如果出现了循环依赖,应该优先重构代码,万一有某种原因无法重构或成本太高,可考虑如下方式:
// before@injectable()export class Foo {constructor(public bar: Bar) {}}@injectable()export class Bar {constructor(public foo: Foo) {}}// after@injectable()export class Foo {constructor(@inject(delay(() => Bar)) public bar: Bar) {}}@injectable()export class Bar {constructor(@inject(delay(() => Foo)) public foo: Foo) {}}// construction of foo is possibleconst foo = container.resolve(Foo);// property bar will hold a proxy that looks and acts as a real Bar instance.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 取:

