第 4 卷
ctx.greeter 读到的已经是新的服务实例(v2),所以输出从 [v1] 变成了 [v2]。“起”
是带着新依赖重新跑一遍初始化逻辑,不是恢复旧状态。
注意:论文里的 activating / deactivating / neutral(激活 / 停用 / 无关)是抽象概念,不是源码里的变量名。源码里对应的,就是这条 notify → _checkImpl → _refresh
- _setEpoch 链,以及 _setEpoch 分叉出的 _unload(停)和 _reload(起)。
把"论文怎么说"和"源码怎么做"分开看,你会清楚得多。
最后,我们看看完整的链路图。
复制代码 1服务被 provide / 撤销
2│ 3▼
4① notify —— 找到声明了这个依赖的插件 5│
6▼
| 7 | ②_checkImpl ——重新查:这个服务还在吗?(没了就删记录,还有就更新记录) |
|---|---|
| 8 | │ |
| 9 | ▼ |
| 10 | ③_refresh ——重算"依赖指纹"(缺了就标INACTIVE) |
| 11 | │ |
| 12 | ▼ |
| 13 | ④_setEpoch ——指纹变了吗? |
| 14 | ├─指纹从「有」变「无」→_unload() →插件停 |
| 15 | └─指纹从「无」变「有」→_reload() →插件起 |
可选依赖
inject 用于硬性依赖。有些场景下,如果某项依赖的服务缺失时插件仍能运行,有它没它都能跑,这个时候就不应该使用 inject,而是手动在使用的地方进行判断处理:
复制代码
6console.log('没有 greeter,跳过这一步')// 没有也能跑 7}
本讲小结
今天这一讲,我们完成了从时间到空间的跨越,这是 Cordis 世界观拼图的第二块。
我们体会到了响应式依赖的魅力:用 Service 提供能力,用 inject 声明依赖,框架便替你接管了“启动编排、停用、重启”的全部时序。我们见证了 consumer 插件在 greeter 下线
时自动停止、在 greeter 恢复时自动重启,全程零调度代码。
这背后真正的变化是:你不再手动编排启动顺序,而是声明“我依赖谁”;让框架根据依赖的满足状态,自动决定激活与停用。
这,就是空之协同的本质——组件之间依赖的编排,从"手动流程"变成了"自动响应"。
至此,时间(可逆 Effect)和空间(响应式依赖)两个维度我们都打通了。但还有一个问题悬而未决:插件之间除了“依赖”(A 需要 B 的能力),还常常需要“通信”(A 要通知 B 发生了
某件事)。依赖是“要”,通信是“说”。下一讲,我们就来聊聊 Cordis 的事件系统——五种分发模式,以及它们各自适合什么场景。
注:课程中说的论文指的是《A Programming Paradigm for Spatiotemporal Composability》,本讲 GitHub 链接
思考题
你来实操一下,照着正文里的 hello-inject.ts 例子,把 consumer 的 inject:
['greeter'] 这一行删掉,重新运行,看看会发生什么。
小改动:在 hello-inject.ts 最后(greeter v2 上线之后),再 dispose 掉这个新的
察当 greeter 缺失时它是静默跳过还是报错——这就是硬依赖(inject)与软依赖(ctx.get)的区别。
欢迎把你的运行结果分享出来。如果你觉得这节课对你有帮助,也欢迎分享给其他朋友,我们下节课再见!
© 版权归极客邦科技所有,未经许可不得传播售卖。页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
全部留言 (1)
最新精选

邋遢的流浪剑客
2026-08-27 来自上海
思考题使用dsh写代码验证结果如下:
第一种情况:删除 consumer 的 inject: ['greeter'],但 apply 里仍然写 ctx.greeter.greet('w orld')。
运行结果是:
[greeter:v1] 已上线
Error: cannot get property "greeter" without inject
结论:会直接报错。
原因是 Cordis 对 ctx.greeter 这种服务属性访问有依赖声明校验。consumer 没有声明 injec t: ['greeter'],就不能直接通过 ctx.greeter 读取 greeter 服务。即使 root 上已经提供了 gre
eter,也会报 cannot get property "greeter" without inject。
所以 inject 不只是表示“等 greeter 服务就绪后再启动 consumer”,它也表示“consumer 允