依赖与资源
L13 Context.Service:类型化的服务键
本课目标
- 会定义并消费一个服务
- 理解 R 通道与 Context 的关系
前置课时尚未完成:L07
可以自行跳转学习,但按顺序推进会更省力;学情分析会把这类缺口归入「前置缺口」。
服务 = 键 + 形状
Context.Service 把服务标识和实现形状定义成一个类。Context 则是以这些键为索引的不可变服务集合。程序通过 yield* SomeService 或 SomeService.use(...) 声明「我需要它」,编译器把该服务记进 R。
定义、使用、提供
← 代码可左右滑动 →
import { Context, Effect, Layer } from "effect"
class Notifications extends Context.Service<
Notifications,
{ readonly notify: (msg: string) => Effect.Effect<void> }
>()("Notifications") {}
Notifications.key // "Notifications"
const action = Notifications.use((n) => n.notify("hello"))
// Effect<void, never, Notifications>
const runnable = action.pipe(
Effect.provide(Layer.succeed(Notifications, {
notify: (msg) => Effect.log(msg)
}))
)
// Effect<void, never, never>💡 服务形状里的方法返回的仍是 Effect,于是错误与依赖可以继续向外累积。
💡 端口与适配器
把 Context.Service 当作端口(接口),把 Layer 当作适配器(实现)。用例代码只依赖端口,测试里换一个 Layer.succeed 就能替换整个外部系统,无需 mock 框架。
交互演练
关键术语
Context.Service
v4 中定义服务键与形状的类
Context.make
手动构造一个 Context<R>
常见误区
× 把服务实例塞进模块级单例,绕开 R 通道,测试时无法替换。
随堂测验(即时反馈,答题计入掌握度)
Q1yield* SomeService 之后,程序类型发生什么变化?
Q2替换服务的实现应该改哪里?