我们可以注意到在我们定义的所有服务中有一个共同的模式:
包含服务实现的
make函数用于服务定义的
Context.Tag类服务类内作为
static属性的Live层
// 1️⃣ `make` 实现
const make = /// ...
// 2️⃣ `Context.Tag` 服务
export class PokeApi extends Context.Tag("PokeApi")<
PokeApi,
Effect.Effect.Success<typeof make>
>() {
// 3️⃣ `Live` 层
static readonly Live = /// ...
}IMPORTANTEffect.Service 的引入
事实证明,这种模式在Effect中无处不在,不仅仅是在本课程的示例中。如此常见,以至于在v3.9中引入了一个API来简化这一切:
Effect.Service。
TIPEffect.Service 的作用
Effect.Service允许在单个类中定义默认服务实现(相当于make)和默认层(相当于Live层)。
https://www.typeonce.dev/course/effect-beginners-complete-getting-started/layers/effect-service-service-and-layer-all-in-one#non-effect-services非Effect服务
让我们从重构 PokemonCollection 服务开始。
import { Context, Layer, type Array } from "effect";
export class PokemonCollection extends Context.Tag("PokemonCollection")<
PokemonCollection,
Array.NonEmptyArray<string>
>() {
static readonly Live = Layer.succeed(this, [
"staryu",
"perrserker",
"flaaffy",
]);
}Effect.Service 在定义上与 Context.Tag 类似:
使用
class定义需要提供服务的唯一名称(作为
string)需要第一个类型参数是服务类型本身
import { Effect } from "effect";
export class PokemonCollection extends Context.Tag("PokemonCollection")<
PokemonCollection,
Array.NonEmptyArray<string>
>() {}
export class PokemonCollection extends Effect.Service<PokemonCollection>()(
"PokemonCollection",
{ /* TODO */ }
) {}我们可以简单地在第二个类型参数中提供默认实现,而不是添加 Live 并为其提供默认实现。
在这种情况下,由于层包含 Array 而不是 Effect,我们可以定义 succeed:
import { Effect } from "effect";
export class PokemonCollection extends Effect.Service<PokemonCollection>()(
"PokemonCollection",
{
succeed: ["staryu", "perrserker", "flaaffy"],
}
) {}NOTE
succeed用于所有之前使用Layer.succeed定义层的服务。
这就是使用 Effect.Service 定义服务所需的全部内容。
TIP类型推断
请注意,使用这种实现,
PokemonCollection的类型被推断为常量数组(readonly ["staryu", "perrserker", "flaaffy"])。
使用Effect定义的服务
对于 BuildPokeApi,唯一的区别是服务被实现为 Effect:
import { Context, Effect, Layer } from "effect";
import { PokeApiUrl } from "./PokeApiUrl";
export class BuildPokeApiUrl extends Context.Tag("BuildPokeApiUrl")<
BuildPokeApiUrl,
({ name }: { name: string }) => string
>() {
static readonly Live = Layer.effect(
this,
Effect.gen(function* () {
const pokeApiUrl = yield* PokeApiUrl;
return ({ name }) => `${pokeApiUrl}/${name}`;
})
).pipe(Layer.provide(PokeApiUrl.Live));
}NOTE注意实现如何使用
Effect.gen,因此需要Layer.effect。
在这些情况下,服务在 effect 内部定义,而不是 succeed:
import { Effect } from "effect";
import { PokeApiUrl } from "./PokeApiUrl";
export class BuildPokeApiUrl extends Effect.Service<BuildPokeApiUrl>()(
"BuildPokeApiUrl",
{
effect: Effect.gen(function* () {
const pokeApiUrl = yield* PokeApiUrl;
return ({ name }: { name: string }) => `${pokeApiUrl}/${name}`;
}),
}
) {}提供依赖
我们仍然需要为服务提供所需的依赖(PokeApiUrl)。之前我们直接使用 Layer.provide:
export class BuildPokeApiUrl extends Context.Tag("BuildPokeApiUrl")<
BuildPokeApiUrl,
({ name }: { name: string }) => string
>() {
static readonly Live = Layer.effect(
this,
Effect.gen(function* () {
const pokeApiUrl = yield* PokeApiUrl;
return ({ name }) => `${pokeApiUrl}/${name}`;
})
).pipe(
Layer.provide(PokeApiUrl.Live),
);
}Effect.Service 允许通过定义 dependencies 来做同样的事情:
export class BuildPokeApiUrl extends Effect.Service<BuildPokeApiUrl>()(
"BuildPokeApiUrl",
{
effect: Effect.gen(function* () {
const pokeApiUrl = yield* PokeApiUrl;
return ({ name }: { name: string }) => `${pokeApiUrl}/${name}`;
}),
dependencies: [PokeApiUrl.Live],
}
) {}返回非对象值的服务
Effect.Service 要求服务类型必须是对象。
NOTE类型限制
具体来说,
Effect.Service接受所有可以分配给class声明中implements的类型。
// 👇 任何在这里有效的类型
class Example implements Record<string, any> {}在我们的示例中,PokeApiUrl 返回一个 string,这不能与 implements 一起使用:
export class PokeApiUrl extends Context.Tag("PokeApiUrl")<
PokeApiUrl,
string
>() {
static readonly Live = Layer.effect(
this,
Effect.gen(function* () {
const baseUrl = yield* Config.string("BASE_URL");
return `${baseUrl}/api/v2/pokemon`;
})
);
}A class cannot implement a primitive type like 'string'.
It can only implement other named object types.ts(2864)在这些情况下,无法使用 Effect.Service,我们仍然需要回退到 Context.Tag。
IMPORTANT设计限制
请注意,在实践中,拥有返回非对象值的服务并不常见或现实。
Effect.Service专门为服务类型设计,而不是为原始值设计。