ArkType 是一个 TypeScript 运行时校验库,它以类似 TypeScript 的字符串编写 schema,并将其编译为经过优化的校验器。对于已拥有稳定 Zod 代码库的团队来说,全面迁移通常得不偿失;但对于新项目或高频调用的校验路径,ArkType 值得一试。
如果你在用 Zod,很可能见过 ArkType 的基准测试图表,并好奇这样的速度提升是否值得去学习一套新语法。
本文将同一个 User schema 从 Zod 迁移到 ArkType,涵盖类型推断、性能、API 响应校验以及各自的取舍,并明确指出何时应继续使用 Zod。示例基于 ArkType 2.2 和 Zod 4。
核心要点
- ArkType 以类似 TypeScript 的字符串定义 schema,因此
"'android' | 'ios'"读起来与其生成的联合类型完全一致;而 Zod 的写法是z.enum(["android", "ios"])。 - 用 ArkType 类型校验未知数据时,返回值要么是校验通过的值,要么是
ArkErrors实例,因此惯用的判断方式是out instanceof type.errors。 - ArkType 官网宣称其运行时性能比 Zod 4 快 20 倍。这是厂商自己给出的数据,而且 Zod 4.5 此后已新增
z.compile(),专门用于优化热点路径。 - ArkType 2.2 支持在
type()中传入任意 Standard Schema 校验器,因此现有的 Zod 4 schema 可以直接嵌套到 ArkType 定义中,无需先行重写。
一句话概括 ArkType 与 Zod 的区别
Zod 通过链式方法调用构建 schema,而 ArkType 以类似 TypeScript 的字符串编写 schema,读起来就像它最终生成的类型。声明为 "(number | string)[]" 的字段,本质上就是你本来就会写的 TypeScript 类型注解,只是加上了引号。ArkType 的“Your First Type”指南指出,编辑器会借助 TypeScript 自身的类型系统,在你输入时实时检查这些字符串定义,并提供自动补全。
同一个 User schema 在 Zod 与 ArkType 中的写法
下面是同一个包含三个字段的 schema 在两个库中的写法:一个必填字符串、一个双值联合类型,以及一个由数字或字符串组成的可选数组。
Zod 4:
import * as z from "zod"
const User = z.object({
name: z.string(),
platform: z.enum(["android", "ios"]),
versions: z.array(z.union([z.number(), z.string()])).optional(),
})
ArkType 2.2:
import { type } from "arktype"
const User = type({
name: "string",
platform: "'android' | 'ios'",
"versions?": "(number | string)[]",
})
ArkType 版本中没有嵌套的 builder 调用。可选标记的位置也不同:ArkType 与 TypeScript 一样,在键上加 ?,而 Zod 则是在值上调用 .optional()。此外,ArkType 还支持全局 exactOptionalPropertyTypes 配置(2.1.12 版本新增),与 TypeScript 中同名的编译选项相对应。
| 对比项 | Zod 4 | ArkType 2.2 |
|---|---|---|
| Schema 语法 | 链式 builder 方法 | 类似 TypeScript 的字符串与对象字面量 |
| 可选字段 | 在值上调用 .optional() | 在键上使用 "key?" |
| 静态类型 | z.infer<typeof User> | typeof User.infer |
| 校验未知数据 | User.safeParse(data) | User(data) |
| 失败判断 | !result.success | out instanceof type.errors |
| 可读的错误信息 | 基于 result.error.issues 构建 | out.summary |
| 编译 | 通过 z.compile() 按需启用(Zod 4.5+) | 内置于定义的处理流程中 |
类型推断:typeof User.infer 与 z.infer
两个库都从运行时 schema 推导静态类型,因此你无需手写 interface,区别仅在于提取类型的语法。
// Zod
type User = z.infer<typeof User>
// ArkType
type User = typeof User.infer
在 Zod 中,z.infer 是作用于 schema 类型的泛型工具类型;在 ArkType 中,infer 是类型本身的一个属性,通过 typeof 读取。对于上面的 schema,两者得到的结构完全相同:{ name: string; platform: "android" | "ios"; versions?: (number | string)[] }。
ArkType 真的比 Zod 快吗?
ArkType 会在每个 Type 创建时预先构建经过优化的校验器。ArkType 配置文档介绍了这一预编译步骤,以及用于关闭它的 jitless 选项。ArkType 官网宣称其运行时性能比 Zod 4 快 20 倍。但这是厂商自己的基准测试结论,并非独立测评结果。
Zod 4.5 随后通过 z.compile() 缩小了部分差距。据 Zod 编译文档介绍,z.compile() 会对 schema 进行一次完整遍历,生成一个线性执行的校验函数,并通过 new Function() 运行。如果输入未能通过这一快速检查,Zod 会将其交给常规解析器处理,因此详细的错误信息保持不变。Zod README 显示,在包含 55 个 schema 的基准测试中,其性能提升的中位数为 2.4 倍。因此,与未编译的 Zod 4 进行对比,并不能说明 ArkType 相对于编译后的 Zod schema 表现如何。
对于表单校验而言,ArkType 与 Zod 之间的速度差异几乎无关紧要。每次用户交互只触发少量校验,不会成为性能瓶颈。只有当同一个 schema 每秒需要执行数千次时(例如在请求处理函数或批量导入场景中),速度才会成为考量因素。
端到端校验 API 响应
实际开发中最常见的任务,是在应用其他部分使用 fetch 响应之前先对其进行校验。以下是同一函数在两个库中的实现。
ArkType:
async function getUser(id: string) {
const res = await fetch(`/api/users/${id}`)
const out = User(await res.json())
if (out instanceof type.errors) {
console.error(out.summary)
return null
}
return out // typed as User
}
Zod:
async function getUser(id: string) {
const res = await fetch(`/api/users/${id}`)
const result = User.safeParse(await res.json())
if (!result.success) {
console.error(result.error.issues)
return null
}
return result.data
}
Zod 与 ArkType 返回的校验结果结构不同。Zod 的 safeParse 返回一个包含 success、data 和 error 的可辨识联合结果对象;而 ArkType 类型要么直接返回校验通过的值,要么返回 ArkErrors 实例。经过 instanceof 判断后,TypeScript 会将 out 收窄为 User。out.summary 是一条可读的汇总信息,列出了所有校验失败的路径、期望值以及实际接收到的值。
ArkErrors 还可以直接传给 JSON.stringify(),这一改动在 ArkType 2.1.10 中引入,并列入了 2.2 的发布说明。这意味着无需事先编写格式化函数,就能将校验失败信息直接写入 API 错误响应或日志记录中。
取舍:生态与熟悉度 vs 一套新语法
Zod 相对于 ArkType 的主要优势在于其周边生态。它拥有庞大的集成生态,而且大多数 TypeScript 开发者早已能熟练阅读 Zod schema。不过,随着 Standard Schema 的出现,这一差距正在缩小——它是 Zod、ArkType 和 Valibot 都实现了的通用接口。在切换之前,请先确认你所用的表单库、路由和 RPC 层是否支持 Standard Schema。
ArkType 的成本也确实存在:
- 一套新语法。 联合类型、数组和可选键看起来很熟悉,但约束条件和更高级的表达式是一门需要团队学习的字符串语言。
- 错误以字符串上的类型错误呈现。 像
"strng"这样的拼写错误会被编辑器中的 TypeScript 捕获,但它表现为字符串定义上的错误,而非方法不存在,因此团队需要适应解读这种新型错误信息。
ArkType 2.2 使渐进式采用成为可能。ArkType 集成文档表明,type() 可以接受任意 Standard Schema 校验器——无论是单独传入还是置于对象定义中——并像原生 ArkType 定义一样对其进行推断和校验。这意味着现有的 Zod 4 schema 可以保持原样:
import * as z from "zod"
import { type } from "arktype"
const ZodDevice = z.object({ platform: z.enum(["android", "ios"]) })
const User = type({
name: "string",
device: ZodDevice, // Standard Schema validator nested in ArkType (2.1.28+)
})
如果你主要关注的是包体积而非速度,那么 Valibot 才是更值得考虑的库。
何时应从 Zod 切换到 ArkType?
对于大多数拥有稳定 Zod 代码库且集成运转良好的团队而言,目前还不应切换到 ArkType。迁移成本高于所获得的速度收益,而这部分收益 Zod 4.5 的编译器已能部分覆盖。在以下情况下,可以尝试 ArkType:
- 启动新项目时,且你的工具链支持 Standard Schema。
- 存在经过验证的热点路径时,例如高吞吐量接口或批处理任务,且性能分析显示校验开销显著。
- 希望在单个接口上试用时,可以将现有 Zod schema 嵌套到 ArkType 定义中,而无需重写。
除此之外,继续使用 Zod 校验数据,并对执行最频繁的 schema 尝试使用 z.compile()。
总结
ArkType 的主要优势在于可读性:schema 看起来就像它所生成的类型,同时还能提供快速的编译型校验器。Zod 的优势则在于它早已融入你的技术栈。若想以低成本验证两者差异,可以选取一个校验密集的接口,用 ArkType 2.2 定义它(同时嵌套现有的 Zod schema),并与同一 Zod schema 的 z.compile() 版本进行性能对比,然后再决定是否做其他改动。
常见问题
ArkType 能在 Cloudflare Workers 或严格的内容安全策略(CSP)下运行吗?
可以。ArkType 会在实例化 Type 时通过 new Function 预编译校验逻辑,并在不支持 new Function 的环境(如 Cloudflare Workers)中自动关闭该功能。在未启用 'unsafe-eval' 的 CSP 下,需将 jitless 选项设为 true。请在从 'arktype' 导入任何内容之前,先通过 'arktype/config' 完成配置,以便内置关键字能够应用该设置。校验功能依然可用,只是不再使用预编译的校验器。
ArkType 能像 Zod 4 一样生成 JSON Schema 吗?
可以。每个 ArkType Type 都有 toJsonSchema() 方法,ArkType 2.2 还新增了 @ark/json-schema 包用于反向转换,即将 JSON Schema 转换为 ArkType Type。对于在 JSON Schema 中没有对应概念的特性(如 morph、symbol 键或 Date),toJsonSchema() 默认会抛出异常,你可以通过 fallback 选项逐一处理这些情况。Zod 4 则通过 z.toJSONSchema() 满足同样的需求。
ArkType 中与 Zod 的 transform() 对应的是什么?
ArkType 将转换称为 morph,并通过 .pipe() 附加,例如 type('string').pipe(s => s.trim())。内置的解析关键字(如 'string.json.parse' 和 'string.numeric.parse')无需回调即可处理常见转换。如果 morph 抛出异常,ArkType 会认为你有意让程序崩溃;若希望将抛出的异常转换为 ArkErrors 结果,请改用 .pipe.try()。推断出的类型会反映 morph 的输出类型。
ArkType 能像 Zod 的 parse() 一样在数据无效时抛出异常,而不是返回错误吗?
可以。对 ArkErrors 结果调用 out.throw() 即可将其抛出;而全局 onFail 选项可以让所有 Type 在数据无效时抛出异常:在 'arktype/config' 中执行 configure({ onFail: errors => errors.throw() })。同时需要在全局 ArkEnv 接口中声明相同的 onFail,以便 TypeScript 知道调用结果不再返回 ArkErrors。默认的返回值风格与 Zod 的 safeParse() 对应,而 onFail 则与 parse() 对应。
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers.
Star on GitHub12k