Skip to content

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.jsonexactOptionalPropertyTypes 选项。此选项影响可选属性的类型化方式(要了解更多关于此选项的信息,您可以参考官方的 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)

@since3.10.0

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)

@since3.10.0

optionalWith
(
import Schema
Schema
.
class NonEmptyString

@since3.10.0

NonEmptyString
, {
exact: true
exact
: true })
})
type
type Type = {
readonly name?: string;
}
Type
=
import Schema
Schema
.
namespace Schema

@since3.10.0

@since3.10.0

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

@since3.10.0

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

@since3.10.0

decodeSync
(
const Person: Schema.Struct<{
name: Schema.optionalWith<typeof Schema.NonEmptyString, {
exact: true;
}>;
}>
Person
)({
name?: string
name
:
var undefined
undefined
})
Error ts(2379) ― Argument of type '{ name: undefined; }' is not assignable to parameter of type '{ readonly name?: string; }' with 'exactOptionalPropertyTypes: true'. Consider adding 'undefined' to the types of the target's properties. Types of property 'name' are incompatible. Type 'undefined' is not assignable to type 'string'.

在这里,注意 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 解码涉及两个关键步骤:

  1. 检查: 首先,我们验证输入数据(类型为 unknown)是否匹配预期结构。在我们的具体情况下,这意味着确保输入确实是 string

  2. 解码: 在成功检查后,我们继续将 string 转换为 Date。此过程完成解码操作,其中数据既被验证又被转换。

unknown 编码涉及两个关键步骤:

  1. 检查: 首先,我们验证输入数据(类型为 unknown)是否匹配预期结构。在我们的具体情况下,这意味着确保输入确实是 Date

  2. 编码: 在成功检查后,我们继续将 Date 转换为 string。此过程完成编码操作,其中数据既被验证又被转换。

在使用模式时,有一个重要的规则要记住:您的模式应该以这样的方式制作,当您执行编码和解码操作时,您应该得到原始值。

简单来说,如果您编码一个值然后立即解码它,结果应该与您开始时的原始值匹配。此规则确保您的数据在整个编码和解码过程中保持一致和可靠。