发布说明
经过一年的积极开发,Zod 4 现已稳定发布!它更快、更轻量、对 tsc 更友好,并且实现了一些长期要求的功能。
非常感谢 Clerk,他们通过非常慷慨的 OSS Fellowship 支持了我对 Zod 4 的工作。在整个(比预期长得多的!)开发过程中,他们都是令人惊叹的合作伙伴。
版本控制
安装:
关于破坏性变更的完整列表,请参考 迁移指南。本文重点介绍新功能和增强。
为什么要发布新的主要版本?
Zod v3.0 发布于 2021 年 5 月(!)。当时 Zod 在 GitHub 上有 2700 个 stars 和每周 60 万次下载。如今它拥有 37.8k stars 和每周 3100 万次下载(比 6 周前 beta 版发布时的 2300 万有所增加!)。经过 24 个次要版本,Zod 3 的代码库已达到瓶颈;最常请求的功能和改进需要破坏性变更。
Zod 4 一举解决了 Zod 3 的许多长期存在的设计限制,为几个长期要求的功能和性能的巨大飞跃铺平了道路。它解决了 Zod 10 个投票最多的未解决问题 中的 9 个。如果运气好的话,它将成为未来更多年的新基础。
欲了解新功能的快速分类,请参阅目录。点击任何项目即可跳转到该部分。
基准测试
你可以在 Zod 仓库中自行运行这些基准测试:
然后运行特定的基准测试:
字符串解析速度提高 14 倍
数组解析速度提高 7 倍
对象解析速度提高 6.5 倍
这运行 Moltar validation library benchmark。
tsc 实例化减少 100 倍
考虑以下简单的文件:
使用 "zod/v3" 运行 tsc --extendedDiagnostics 编译此文件会导致超过 25000 次类型实例化。而使用 "zod/v4" 仅产生约 175 次。
Zod 仓库包含一个 tsc 基准测试游乐场。你可以使用 packages/tsc 中的编译器基准测试自行尝试。确切的数字可能会随着实现的演变而变化。
更重要的是,Zod 4 重新设计并简化了 ZodObject 和其他 schema 类的泛型,以避免一些恶性的“实例化爆炸”。例如,重复链式调用 .extend() 和 .omit()——这在以前会导致编译器问题:
在 Zod 3 中,这需要 4000ms 来编译;添加更多的 .extend() 调用会触发“可能无限”的错误。在 Zod 4 中,这在 400ms 内编译完成,快了 10x。
结合即将推出的 tsgo 编译器,Zod 4 的编辑器性能将扩展到更庞大的 schema 和代码库。
核心包大小减少 2 倍
考虑以下简单的脚本。
这大概是验证中最简单的情况了。这是有意的;这是衡量 核心包大小 的好方法——即即使在简单情况下也会包含在包中的代码。我们将使用 rollup 分别打包 Zod 3 和 Zod 4 并比较最终的包。
| Package | Bundle (gzip) |
|---|---|
| Zod 3 | 12.47kb |
| Zod 4 | 5.36kb |
Zod 4 的核心包大约小 57% (2.3x)。这很好!但我们可以做得更好。
介绍 Zod Mini
Zod 的重方法 API 在根本上很难 tree-shake。即使是我们简单的 z.boolean() 脚本也会引入一堆我们未使用的包括 .optional()、.array() 等方法的实现。编写更精简的实现只能走到这一步。这就是 Zod Mini 的用武之地。
它是 Zod 的一个变体,具有函数式的、可 tree-shake 的 API,与 zod 一一对应。Zod 使用方法的地方,Zod Mini 通常使用包装函数:
并非所有方法都消失了!解析方法在 Zod 和 Zod Mini 中是相同的:
还有一个通用的 .check() 方法用于添加细化 (refinements)。
Zod Mini 中提供了以下顶级细化。它们对应的 Zod 方法应该是不言自明的。
这种更函数式的 API 使打包器更容易 tree-shake 掉你未使用的 API。虽然对于大多数用例仍建议使用常规 Zod,但任何具有非同寻常的严格包大小限制的项目都应考虑 Zod Mini。
核心包大小减少 6.6 倍
这是上面的脚本,已更新为使用 "zod/mini" 而不是 "zod"。
当我们使用 rollup 构建它时,gzip 压缩后的包大小为 1.88kb。与 zod@3 相比,核心包大小减少了 85% (6.6x)。
| Package | Bundle (gzip) |
|---|---|
| Zod 3 | 12.47kb |
| Zod 4 (regular) | 5.36kb |
| Zod 4 (mini) | 1.88kb |
欲了解更多信息,请参阅专门的 zod/mini 文档页面。完整的 API 细节混合在现有的文档页面中;在 API 不同的地方,代码块包含 "Zod" 和 "Zod Mini" 的单独标签页。
元数据 (Metadata)
Zod 4 引入了一个新系统,用于向 schema 添加强类型的元数据。元数据不存储在 schema 本身内部;而是存储在一个“schema 注册表”中,该注册表将 schema 与某些类型化的元数据相关联。使用 z.registry() 创建注册表:
要将 schema 添加到你的注册表:
或者,为了方便,你可以在 schema 上使用 .register() 方法:
全局注册表
Zod 还导出一个全局注册表 z.globalRegistry,它接受一些通用的 JSON Schema 兼容的元数据:
.meta()
为了方便地将 schema 添加到 z.globalRegistry,请使用 .meta() 方法。
为了与 Zod 3 兼容,.describe() 仍然可用,但首选 .meta()。
JSON Schema 转换
Zod 4 通过 z.toJSONSchema() 引入了第一方 JSON Schema 转换。
z.globalRegistry 中的任何元数据都会自动包含在 JSON Schema 输出中。
有关自定义生成的 JSON Schema 的信息,请参阅 JSON Schema 文档。
递归对象
这是一个意外的惊喜。经过多年尝试解决这个问题,我终于 找到了一个方法 来正确推断 Zod 中的递归对象类型。定义递归类型:
你也可以表示 相互递归类型:
与 Zod 3 的递归类型模式不同,不需要类型转换。生成的 schema 是普通的 ZodObject 实例,并且具有可用的完整方法集。
文件 Schemas
要验证 File 实例:
国际化
Zod 4 引入了一个新的 locales API,用于将错误消息全局翻译成不同语言。
请参阅 错误自定义 中的完整支持语言列表;随着新语言的加入,该部分将始终更新。
错误美化打印
zod-validation-error 包的流行证明了对官方美化打印错误 API 的巨大需求。如果你正在使用该包,请务必继续使用。
Zod 现在实现了一个顶级 z.prettifyError 函数,用于将 ZodError 转换为用户友好的格式化字符串。
这将返回以下可美化打印的多行字符串:
目前格式不可配置;这可能在未来发生变化。
顶级字符串格式
所有“字符串格式”(email 等)都已提升为 z 模块上的顶级函数。这既更简洁,又更易于 tree-shake。方法等价物(z.string().email() 等)仍然可用,但已弃用。它们将在下一个主要版本中删除。
自定义 Email 正则表达式
z.email() API 现在支持自定义正则表达式。没有一种规范的 email 正则;不同的应用程序可能会选择更严格或更宽松。为了方便,Zod 导出了一些通用的正则。
模板字面量类型
Zod 4 实现了 z.templateLiteral()。模板字面量类型可能是 TypeScript 类型系统中以前无法表示的最大功能。
每个可以字符串化的 Zod schema 类型都存储一个内部正则:字符串、字符串格式如 z.email()、数字、布尔值、bigint、枚举、字面量、undefined/optional、null/nullable 和其他模板字面量。z.templateLiteral 构造函数将这些连接成一个超级正则,因此像字符串格式 (z.email()) 这样的东西会被正确强制执行(但自定义细化不会!)。
阅读 模板字面量文档 了解更多信息。
数字格式
添加了新的数字“格式”来表示固定宽度的整数和浮点类型。这些返回一个 ZodNumber 实例,并通过已添加的适当最小/最大约束。
同样,也添加了以下 bigint 数字格式。这些整数类型超出了 JavaScript 中 number 可以安全表示的范围,因此这些返回一个 ZodBigInt 实例,并通过已添加的适当最小/最大约束。
Stringbool
现有的 z.coerce.boolean() API 非常简单:falsy 值(false, undefined, null, 0, "", NaN 等)变为 false,truthy 值变为 true。
这仍然是一个很好的 API,其行为与其他 z.coerce API 一致。但一些用户请求更复杂的“env 风格”布尔强制转换。为了支持这一点,Zod 4 引入了 z.stringbool():
要自定义 truthy 和 falsy 值:
参阅 z.stringbool() 文档 了解更多信息。
简化的错误自定义
Zod 4 中的大多数破坏性变更都涉及 错误自定义 API。它们在 Zod 3 中有点混乱;Zod 4 使事情变得更加优雅,我认为这值得在这里强调。
长话短说,现在有一个统一的 error 参数用于自定义错误,以替换以下 API:
将 message 替换为 error。(message 参数仍然支持但已弃用。)
将 invalid_type_error 和 required_error 替换为 error(函数语法):
将 errorMap 替换为 error(函数语法):
升级的 z.discriminatedUnion()
受歧视联合 (Discriminated unions) 现在支持许多以前不支持的 schema 类型,包括联合 (unions) 和管道 (pipes):
也许最重要的是,受歧视联合现在可以 组合——你可以使用一个受歧视联合作为另一个的成员。
z.literal() 中的多个值
z.literal() API 现在可选地支持多个值。
细化 (Refinements) 存在于 Schema 内部
在 Zod 3 中,它们存储在包装原始 schema 的 ZodEffects 类中。这很不方便,这意味着你不能将 .refine() 与其他 schema 方法(如 .min())交错使用。
在 Zod 4 中,细化存储在 schema 本身内部,因此上面的代码按预期工作。
.overwrite()
.transform() 方法非常有用,但它有一个主要缺点:输出类型在运行时不再是 可内省的。转换函数是一个可以返回任何东西的黑盒。这意味着(除其他外)没有可靠的方法将 schema 转换为 JSON Schema。
Zod 4 引入了一个新的 .overwrite() 方法来表示 不改变推断类型 的转换。与 .transform() 不同,此方法返回原始类的实例。overwrite 函数存储为细化,因此它不会(也不能)修改推断类型。
现有的 .trim(), .toLowerCase() 和 .toUpperCase() 方法已使用 .overwrite() 重新实现。
可扩展的基础:zod/v4/core
虽然这与大多数 Zod 用户无关,但值得强调。Zod Mini 的添加需要创建一个共享的子包 zod/v4/core,其中包含 Zod 和 Zod Mini 之间共享的核心功能。
起初我很抵触这一点,但现在我认为它是 Zod 4 最重要的功能之一。它让 Zod 从一个简单的库升级为一个快速验证“基底”,可以撒入其他库中。
如果你正在构建一个 schema 库,请参考 Zod 和 Zod Mini 的实现,了解如何构建在 zod/v4/core 提供的基础之上。如果有需要帮助或反馈,请不要犹豫,在 GitHub discussions 或通过 X/Bluesky 联系。
总结
我计划写一系列额外的文章来解释 Zod Mini 等主要功能背后的设计过程。随着这些文章的发布,我会更新此部分。
对于库作者,现在有一个专门的 For library authors 指南,描述了在 Zod 之上构建的最佳实践。它回答了有关如何同时支持 Zod 3 和 Zod 4(包括 Mini)的常见问题。
解析愉快!
— Colin McDonnell @colinhacks

