525 words
3 minutes
[Effect Services] 02. Context:依赖注入

Context:依赖注入#

TIP

依赖注入模式

我们的目标是围绕”抽象接口”组织我们的代码库,每个接口定义我们 API 的签名。

然后根据环境(测试、开发、生产),我们构建并提供具体的实现。

这种模式称为依赖注入

Effect 服务专为依赖注入而设计。不仅如此,Effect 还使依赖注入完全类型安全

Effect 中的服务:Context#

NOTE

Context 工作原理

通用服务使用 Context 定义。

Context 实现为一个全局 Map按键存储服务实现

当我们提供具体实现时,Effect 从这个全局 Map 中提取服务(全部类型安全)。

通常的做法是在与服务同名的文件中定义每个服务。因此我们为新服务创建一个新的 PokeApi.ts 文件。

我个人使用 Pascal 命名法为每个服务命名,在这个例子中是 PokeApi

我们首先使用简单的 interface 定义我们 API 的签名。这包含服务提供的所有方法

PokeApi.ts

import { Effect, type ParseResult } from "effect";
import type { ConfigError } from "effect/ConfigError";
import type { FetchError, JsonError } from "./errors";
import type { Pokemon } from "./schemas";

export interface PokeApi {
  readonly getPokemon: Effect.Effect<
    Pokemon,
    FetchError | JsonError | ParseResult.ParseError | ConfigError
  >;
}

手动定义 interface 比较冗长。别担心,有更简洁的方法,现在请先忍耐一下。

基于这个 interface,我们使用 Context.GenericTag 创建服务:

  • 我们传递一个键 string,用于在全局 Map 中标识服务("PokeApi"
import { Effect, Context } from "effect";

export interface PokeApi {
  readonly getPokemon: Effect.Effect<
    typeof Pokemon.Type,
    FetchError | JsonError | ParseResult.ParseError | ConfigError
  >;
}

export const PokeApi = Context.GenericTag<PokeApi>("PokeApi");

interfaceContext 服务使用相同的名称(PokeApi)很方便。这允许通过单个导入将服务既作为类型又作为值导出和使用。

IMPORTANT

可组合性的力量

这就是定义服务所需的全部内容。如您所见,我们还没有定义任何具体实现。尽管如此,我们已经可以开始使用该服务了。实现可以稍后定义。这就是可组合性和依赖注入的力量!

[Effect Services] 02. Context:依赖注入
https://0bipinnata0.my/posts/course/effect-beginners-complete-getting-started/effect-services/02-context-dependency-injection/
Author
0bipinnata0
Published at
2025-08-30 17:48:32