Effect Schema 介绍
欢迎来到 effect/Schema 的文档,这是一个用于定义和使用模式来验证和转换 TypeScript 数据的模块。
effect/Schema 模块允许您定义一个 Schema<Type, Encoded, Requirements>,它提供了描述数据结构和数据类型的蓝图。一旦定义,您可以利用此模式执行一系列操作,包括:
| 操作 | 描述 |
|---|---|
| 解码 | 将数据从输入类型 Encoded 转换为输出类型 Type。 |
| 编码 | 将数据从输出类型 Type 转换回输入类型 Encoded。 |
| 断言 | 验证值是否符合模式的输出类型 Type。 |
| 标准模式 | 生成 Standard Schema V1。 |
| 任意值 | 为 fast-check 测试生成任意值。 |
| JSON 模式 | 基于定义的模式创建 JSON 模式。 |
| 等价性 | 基于定义的模式创建等价性。 |
| 美化打印 | 支持数据结构的美化打印。 |
- TypeScript 5.4 或更新版本。
- 在您的
tsconfig.json文件中启用strict标志。 - (可选)在您的
tsconfig.json文件中启用exactOptionalPropertyTypes标志。
{ "compilerOptions": { "strict": true, "exactOptionalPropertyTypes": true // optional }}effect/Schema 模块利用了 tsconfig.json 的 exactOptionalPropertyTypes 选项。此选项影响可选属性的类型化方式(要了解更多关于此选项的信息,您可以参考官方的 TypeScript 文档)。
示例(启用 exactOptionalPropertyTypes)
import { import Schema
Schema } from "effect"
const const Person: Schema.Struct<{ name: Schema.optionalWith<typeof Schema.NonEmptyString, { exact: true; }>;}>
Person = import Schema
Schema.function Struct<{ name: Schema.optionalWith<typeof Schema.NonEmptyString, { exact: true; }>;}>(fields: { name: Schema.optionalWith<typeof Schema.NonEmptyString, { exact: true; }>;}): Schema.Struct<{ name: Schema.optionalWith<typeof Schema.NonEmptyString, { exact: true; }>;}> (+1 overload)
Struct({ name: Schema.optionalWith<typeof Schema.NonEmptyString, { exact: true;}>
name: import Schema
Schema.const optionalWith: <typeof Schema.NonEmptyString, { exact: true;}>(self: typeof Schema.NonEmptyString, options: { exact: true;}) => Schema.optionalWith<typeof Schema.NonEmptyString, { exact: true;}> (+1 overload)
optionalWith(import Schema
Schema.class NonEmptyString
NonEmptyString, { exact: true
exact: true })})
type type Type = { readonly name?: string;}
Type = import Schema
Schema.namespace Schema
Schema.type Schema<in out A, in out I = A, out R = never>.Type<S> = S extends Schema.Schema.Variance<infer A, infer _I, infer _R> ? A : never
Type<typeof const Person: Schema.Struct<{ name: Schema.optionalWith<typeof Schema.NonEmptyString, { exact: true; }>;}>
Person>/*type Type = { readonly name?: string;}*/
import Schema
Schema.decodeSync<{ readonly name?: string;}, { readonly name?: string;}>(schema: Schema.Schema<{ readonly name?: string;}, { readonly name?: string;}, never>, options?: ParseOptions): (i: { readonly name?: string;}, overrideOptions?: ParseOptions) => { readonly name?: string;}export decodeSync
decodeSync(const Person: Schema.Struct<{ name: Schema.optionalWith<typeof Schema.NonEmptyString, { exact: true; }>;}>
Person)({ name?: string
name: var undefined
undefined })Error ts(2379) ― 在这里,注意 name 的类型是”精确的”(string),这意味着类型检查器将捕获任何尝试分配无效值(如 undefined)的行为。
示例(禁用 exactOptionalPropertyTypes)
如果由于某种原因,您无法启用 exactOptionalPropertyTypes 选项(可能是由于与其他第三方库的冲突),您仍然可以使用 effect/Schema。但是,类型和运行时行为之间会存在不匹配:
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.optionalWith(Schema.NonEmptyString, { exact: true })})
type Type = Schema.Schema.Type<typeof Person>/*type Type = { readonly name?: string | undefined;}*/
// 没有类型错误,但会发生解码失败Schema.decodeSync(Person)({ name: undefined })/*抛出:ParseError: { readonly name?: NonEmptyString }└─ ["name"] └─ NonEmptyString └─ From side refinement failure └─ Expected string, actual undefined*/在这种情况下,name 的类型被扩展为 string | undefined,这意味着类型检查器不会捕获无效值(undefined)。但是,在解码过程中,您会遇到错误,表明不允许 undefined。
Schema 类型表示一个描述数据结构的不可变值。
以下是 Schema 的一般形式:
┌─── Type of the decoded value │ ┌─── Encoded type (input/output) │ │ ┌─── Requirements (context) ▼ ▼ ▼Schema<Type, Encoded, Requirements>Schema 类型有三个类型参数,含义如下:
| 参数 | 描述 |
|---|---|
| Type | 表示模式在解码期间可以成功处理的值的类型。 |
| Encoded | 表示模式在编码期间可以成功处理的值的类型。默认情况下,如果没有明确提供,它等于 Type。 |
| Requirements | 类似于 Effect 类型,它表示模式执行解码和编码所需的上下文数据。如果此类型参数为 never(如果没有明确提供则为默认值),则意味着模式没有要求。 |
示例
Schema<string>(默认为Schema<string, string, never>)表示一个解码为string、编码为string且没有要求的模式。Schema<number, string>(默认为Schema<number, string, never>)表示一个从string解码为number、将number编码为string且没有要求的模式。
不可变性。Schema 值是不可变的,effect/Schema 模块中的每个函数都会产生一个新的 Schema 值。
建模数据结构。这些值本身不执行任何操作,它们只是建模或描述数据的结构。
编译器解释。Schema 可以被各种”编译器”解释为特定操作,具体取决于编译器类型(解码、编码、美化打印、任意值等)。
在 TypeScript 中处理数据时,您经常需要处理来自外部系统或发送到外部系统的数据。这些数据可能并不总是匹配您期望的格式或类型,特别是在处理用户输入、来自 API 的数据或以不同格式存储的数据时。为了处理这些差异,我们使用解码和编码。
| 术语 | 描述 |
|---|---|
| 解码 | 用于解析来自外部源的数据,您无法控制数据格式。 |
| 编码 | 用于将数据发送到外部源时,将其转换为这些源期望的格式。 |
例如,在前端处理表单时,您经常以字符串形式接收无类型数据。这些数据可能被篡改,并且本身不支持数组或布尔值。解码帮助您验证并将这些数据解析为更有用的类型,如数字、日期和数组。编码允许您将这些类型转换回表单期望的字符串格式。
下面是一个图表,显示了使用 Schema<A, I, R> 进行编码和解码的关系:
┌─────────┐ ┌───┐ ┌───┐ ┌─────────┐| unknown | | A | | I | | unknown |└─────────┘ └───┘ └───┘ └─────────┘ | | | | | validate | | | |─────────────►│ | | | | | | | is | | | |─────────────►│ | | | | | | | asserts | | | |─────────────►│ | | | | | | | encodeUnknown| | | |─────────────────────────►| | | | | | encode | | |──────────►│ | | | | | decode | | | ◄─────────| | | | | | | decodeUnknown| | ◄────────────────────────|我们将使用 Schema<Date, string, never> 的示例来分解这些概念。此模式用作将 string 转换为 Date 的工具,反之亦然。
当我们谈论”编码”时,我们指的是将 Date 转换为 string 的过程。简单来说,这是将数据从一种格式转换为另一种格式的行为。
相反,“解码”涉及将 string 转换回 Date。这本质上是编码的逆操作,数据被返回到其原始形式。
从 unknown 解码涉及两个关键步骤:
-
检查: 首先,我们验证输入数据(类型为
unknown)是否匹配预期结构。在我们的具体情况下,这意味着确保输入确实是string。 -
解码: 在成功检查后,我们继续将
string转换为Date。此过程完成解码操作,其中数据既被验证又被转换。
从 unknown 编码涉及两个关键步骤:
-
检查: 首先,我们验证输入数据(类型为
unknown)是否匹配预期结构。在我们的具体情况下,这意味着确保输入确实是Date。 -
编码: 在成功检查后,我们继续将
Date转换为string。此过程完成编码操作,其中数据既被验证又被转换。
在使用模式时,有一个重要的规则要记住:您的模式应该以这样的方式制作,当您执行编码和解码操作时,您应该得到原始值。
简单来说,如果您编码一个值然后立即解码它,结果应该与您开始时的原始值匹配。此规则确保您的数据在整个编码和解码过程中保持一致和可靠。