服务间通信方案
本文梳理在
use-vue-service中,不同层级关系的服务之间如何进行通信。 核心原则:优先通过容器继承关系和 DI 注入解决通信问题,尽量避免使用 EventEmitter、mitt 等事件总线方案。
一、同一容器中的服务通信
A、B 两个服务注册在同一个容器中,可以直接互相注入,调用对方的方法或访问对方的响应式属性,没有任何限制。
ts
@Injectable()
class ServiceA {
@Inject(ServiceB)
private b!: ServiceB;
doSomething() {
this.b.someMethod();
}
}
@Injectable()
class ServiceB {
@Inject(ServiceA)
private a!: ServiceA;
doSomething() {
this.a.someMethod();
}
}注意循环注入在技术上可行,但需要确保不会在构造函数中触发循环调用。
二、父子容器中的服务通信
2.1 子服务注入父服务(向上查找)
子容器中的服务可以直接注入父容器中的服务,这是 DI 容器继承链的自然能力。
ts
// 父容器中注册 ServiceA
declareProviders([ServiceA]);
// 子容器中注册 ServiceB,ServiceB 可以直接注入 ServiceA
@Injectable()
class ServiceB {
@Inject(ServiceA)
private a!: ServiceA;
}2.2 父服务调用子服务(向下查找)
父容器中的服务无法直接注入子容器中的服务(子容器的生命周期短于父容器,长期持有子服务的引用是危险的)。
推荐使用 FIND_CHILD_SERVICE 进行瞬态查找:在需要调用子服务的函数内部实时查找,函数执行完毕后引用自动释放。
ts
@Injectable()
class ServiceA {
@Inject(FIND_CHILD_SERVICE)
private findChild!: FindChildService;
handleClick() {
// 在函数内部实时查找,不在函数外部长期持有引用
const b = this.findChild(ServiceB);
if (b) {
b.someMethod();
}
}
}关键约束:只能在函数内部使用,不能将查找结果保存到实例属性上长期持有,因为子服务所在组件随时可能被销毁。
三、平级容器中的服务通信
A、B 两个服务分别注册在两个不同的子容器中,无法直接互相注入。有以下两种解决方式:
3.1 共享状态提升
将需要共享的状态提升到公共父容器的某个服务中,A、B 各自注入该服务来读写共享状态。
ts
// 公共父容器中注册 SharedService
@Injectable()
class SharedService {
sharedData = ref('');
}
// A 和 B 分别注入 SharedService 来共享状态
@Injectable()
class ServiceA {
@Inject(SharedService)
private shared!: SharedService;
}
@Injectable()
class ServiceB {
@Inject(SharedService)
private shared!: SharedService;
}3.2 通过父容器中转方法调用
如果需要跨平级容器调用对方的方法,可以通过公共父容器中的中转服务来实现:A 调用父服务的方法,父服务通过 FIND_CHILD_SERVICE 找到 B,再调用 B 的方法。
ts
@Injectable()
class ParentService {
@Inject(FIND_CHILD_SERVICE)
private findChild!: FindChildService;
triggerB() {
const b = this.findChild(ServiceB);
if (b) {
b.someMethod();
}
}
}
@Injectable()
class ServiceA {
@Inject(ParentService)
private parent!: ParentService;
handleClick() {
this.parent.triggerB();
}
}四、为什么避免事件总线
EventEmitter、mitt 等事件总线方案虽然灵活,但存在以下问题:
- 依赖关系隐式:通过字符串事件名通信,IDE 无法追踪调用链,重构困难
- 类型安全弱:事件载荷的类型难以约束,容易出现运行时错误
- 生命周期管理复杂:需要手动在组件/服务销毁时取消订阅,容易遗漏导致内存泄漏
- 与 DI 体系割裂:事件总线是全局状态,与容器的作用域隔离机制冲突
通过 DI 注入和容器继承关系进行通信,依赖关系显式可追踪,类型安全,生命周期由容器统一管理,是更符合本库设计理念的通信方式。