Effect v4 里 Effect.Service 用什么替代?

在 effect 4.0.5 里,Effect.Service、Context.Tag、Context.GenericTag、Layer.scoped 全部不存在,tsc 会直接报属性不存在。

替代是两个名字:声明服务只用 Context.Service,构造 Layer 只用 Layer.effect。

这是同一个变化的两面:服务只有一种声明方式,Layer 构造也只有一个入口,Scope 的处理由 Layer.effect 的类型自动扣掉。下面用一个会连接、发送、断开的 Mailer 示例,把这几件事的写法和行为讲清楚。需要先了解服务与 Layer 是什么,可以看 Effect 服务与 Layer:把依赖写进类型里。

旧写法分别换成什么?

  • Effect.Service 与 Context.Tag、Context.GenericTag:统一用 Context.Service。
  • Layer.scoped:用 Layer.effect。它的类型会把 Scope 从需求里扣掉,构建 Layer 时用 acquireRelease 注册的资源,会在 Layer 的作用域关闭时释放。
  • v3 的 Effect.Tag 式静态访问器没有了,取而代之的是 Service.use(f)。
  • Layer.succeed 两种写法都能用:Layer.succeed(Tag, impl) 和 Layer.succeed(Tag)(impl)。

没有 Layer.scoped,资源还会被释放吗?

会。下面的例子在 Layer 里 acquireRelease 一个「连接」,然后用 ManagedRuntime 运行一次:

// mailer.ts
import { Context, Effect, Layer, ManagedRuntime } from "effect"

export const events: Array<string> = []

export class Mailer extends Context.Service<Mailer, {
  readonly send: (to: string) => Effect.Effect<void>
}>()("app/Mailer") {
  static readonly layer = Layer.effect(
    Mailer,
    Effect.gen(function* () {
      yield* Effect.acquireRelease(
        Effect.sync(() => events.push("connect")),
        () => Effect.sync(() => events.push("disconnect"))
      )
      return Mailer.of({ send: (to) => Effect.sync(() => events.push(`send ${to}`)) })
    })
  )
}

export const notify = Mailer.use((mailer) => mailer.send("a@example.com"))

export const runOnce = async () => {
  const runtime = ManagedRuntime.make(Mailer.layer)
  await runtime.runPromise(notify)
  await runtime.dispose()
}

跑完之后 events 是 connect、send a@example.com、disconnect:资源在 dispose 时按顺序释放,没有 Layer.scoped 也没有手动的 Effect.scoped。

Mailer.use(f) 就是上面说的静态访问器的替代:拿到服务实例,交给你的函数。

构造逻辑能放在服务类上吗?

能。Context.Service 接受一个 make 选项,把构造逻辑放在服务类上,之后用 Layer.effect(Self, Self.make) 组装:

// counter.ts
import { Context, Effect, Layer, Ref } from "effect"

export class Counter extends Context.Service<Counter, {
  readonly next: Effect.Effect<number>
}>()("app/Counter", {
  make: Effect.gen(function* () {
    const ref = yield* Ref.make(0)
    return { next: Ref.updateAndGet(ref, (n) => n + 1) }
  })
}) {
  static readonly layer = Layer.effect(Counter, Counter.make)
}

v3 的 dependencies 和 accessors 选项呢?

v3 的 Effect.Service 里的 dependencies 与 accessors 选项也一并没有了:依赖用 Layer.provide 接,访问用 use。

运行时那一侧要改什么?

Runtime.runPromise 之类的函数在 4.0.5 里不存在,长驻宿主用 ManagedRuntime.make,写法见上面的 runOnce。运行器该放在哪、测试怎么写,见 Effect 运行时边界与测试:只在入口 run,用 TestClock 测时间。整体迁移顺序见 Effect 从 v3 升级到 v4 要改哪些地方?。

版本是 effect 4.0.5,被移除的 API 用 tsc 确认过不存在;上面的 Mailer 示例在该版本上编译并运行过,事件顺序是实际结果。v3 一侧的行为用 effect 3.22.3 对照确认过。dependencies 与 accessors 选项的结论来自同一轮核对。