934 words
5 minutes
[Effect Schema] 04. 使用Schema.Class定义Schema

使用Schema.Class定义Schema#

https://github.com/typeonce-dev/effect-getting-started-course

如果你在 IDE 中检查 program 的成功类型,你实际上看不到 Pokemon,而是看到一个包含我们在 Pokemon 内部定义的属性的对象。

https://vmfiooakcvcmnormoutn.supabase.co/storage/v1/object/public/sandromaglione-com/blog-images/effect-schema-decode-type-response

成功类型包含完整的 Pokemon interface 及其所有属性

这个问题源于我们定义 Schema 的方式。使用 Schema.Struct 我们无法获得不透明类型

不透明类型是一种底层结构被隐藏的类型。它就像一个黑盒子:你知道它代表什么,但不知道其内部细节。

使用 Schema.Struct 我们反而得到了类型的完整结构!

使用class和Schema.Class定义Schema#

我们可以使用 classSchema.Class,这允许同时定义形状并导出不透明类型

  • 定义一个继承 Schema.Classclass

  • 类型参数与 class 名称相同(<Pokemon>

  • string 参数是 Schema 的 _tag

  • 第二个参数是 Schema 的形状

class Pokemon extends Schema.Class<Pokemon>("Pokemon")({ // 👇 参数与 `Schema.Struct` 相同 id: Schema.Number, order: Schema.Number, name: Schema.String, height: Schema.Number, weight: Schema.Number, }) {}

现在 Pokemon 可以直接用作类型:

class Pokemon extends Schema.Class<Pokemon>("Pokemon")({ id: Schema.Number, order: Schema.Number, name: Schema.String, height: Schema.Number, weight: Schema.Number, }) {} const extractId = (pokemon: Pokemon) => pokemon.id;

由于我们现在使用的是 class,我们也可以为其附加方法:

class Pokemon extends Schema.Class<Pokemon>("Pokemon")({ id: Schema.Number, order: Schema.Number, name: Schema.String, height: Schema.Number, weight: Schema.Number, }) { public get formatHeight(): string { return `${this.height}cm`; } }

我们还解决了 IDE 中不透明类型的问题。使用 Schema.Class 时,当你检查响应类型时,你会看到 Pokemon 而不是所有属性:

https://vmfiooakcvcmnormoutn.supabase.co/storage/v1/object/public/sandromaglione-com/blog-images/schema-class-effect-opaque-type-ide

Schema.Class 定义了不透明类型,使 IDE 中的类型更易读

在可能的情况下总是使用 Schema.Class 来定义我的 Schema。

Schema.Class 不能用于非对象 Schema,如联合类型或原始类型。

/// 联合值使用不带 `Class` 的 `Schema.Literal` const PokemonType = Schema.Literal("fire", "water", "grass"); /// ⛔️ 不能使用 `Schema.Class` ⛔️ class PokemonType extends Schema.Class<PokemonType>("PokemonType")(Schema.Literal("fire", "water", "grass")) {}

这是我们现在的应用:

index.ts

import { Schema } from "effect"; import { Data, Effect } from "effect"; /** Schema 定义 **/ class Pokemon extends Schema.Class<Pokemon>("Pokemon")({ id: Schema.Number, order: Schema.Number, name: Schema.String, height: Schema.Number, weight: Schema.Number, }) {} /** 错误类型 **/ class FetchError extends Data.TaggedError("FetchError")<{}> {} class JsonError extends Data.TaggedError("JsonError")<{}> {} /** 实现 **/ const fetchRequest = Effect.tryPromise({ try: () => fetch("https://pokeapi.co/api/v2/pokemon/garchomp/"), catch: () => new FetchError(), }); const jsonResponse = (response: Response) => Effect.tryPromise({ try: () => response.json(), catch: () => new JsonError(), }); const decodePokemon = Schema.decodeUnknown(Pokemon); const program = Effect.gen(function* () { const response = yield* fetchRequest; if (!response.ok) { return yield* new FetchError(); } const json = yield* jsonResponse(response); return yield* decodePokemon(json); }); /** 错误处理 **/ const main = program.pipe( Effect.catchTags({ FetchError: () => Effect.succeed("Fetch error"), JsonError: () => Effect.succeed("Json error"), ParseError: () => Effect.succeed("Parse error"), }) ); /** 运行 Effect **/ Effect.runPromise(main).then(console.log);

Effect Playground

当我们运行这个程序时,我们得到以下结果:

> effect-getting-started-course@1.0.0 dev > tsx src/index.ts { id: 445, order: 570, name: 'garchomp', height: 19, weight: 950 }

我们在原始的纯 TypeScript 解决方案基础上增加了相当多的代码行。尽管如此,这使我们能够从”快乐路径”转向完整的错误处理和 Schema 验证。

此外,这个应用是完全类型安全的。没有什么会在我们不知情的情况下出错,因为编译器会报告任何错误并阻止应用启动。

这太棒了!在实践中,这意味着不再有运行时错误。

我们有一句话:“如果能编译,就能工作”

我们还不满足。我们仍在硬编码像 API url(https://pokeapi.co)这样的值:

const fetchRequest = Effect.tryPromise({ try: () => fetch("https://pokeapi.co/api/v2/pokemon/garchomp/"), catch: () => new FetchError(), });

这在测试和维护应用时会造成问题:

  • 我们如何为测试更改 url?

  • 我们不想每次使用时都复制粘贴 url

  • 如果 url 因任何原因发生变化,很难重构

这使得随着应用规模的扩大,测试和组织代码变得困难。有一个解决方案:环境变量

让我们看看如何在 Effect 中使用 Config 来管理它们!

[Effect Schema] 04. 使用Schema.Class定义Schema
https://0bipinnata0.my/posts/course/effect-beginners-complete-getting-started/effect-schema/04-define-schema-with-schema-class/
Author
0bipinnata0
Published at
2025-08-30 17:10:58