运行时校验选型:Zod vs Valibot vs ArkType
一句话概述:三大运行时校验库的 API 设计、包体积、性能、tree-shaking 能力对比——帮你做出 2026 年的最佳选择
为什么需要对比?
2026 年 TypeScript 运行时校验库的格局已经从"Zod 一家独大"变为"三足鼎立"。选择合适的库直接影响项目的包体积、开发体验和性能。
一、核心对比
基本信息
| 维度 | Zod | Valibot | ArkType |
|---|---|---|---|
| 首次发布 | 2020 | 2023 | 2023 |
| 设计理念 | 链式 API,一体化 | 函数式,可组合,tree-shakable | 类型优先,接近 TS 语法 |
| 包体积(minified) | ~57KB | ~7KB(按需引入更小) | ~37KB |
| Tree-shaking | ❌ 较差(class-based) | ✅ 优秀(function-based) | ⚠️ 中等 |
| TypeScript 支持 | ✅ 内置 | ✅ 内置 | ✅ 内置 |
| 生态集成 | 最丰富(tRPC、RHF、Next.js) | 快速增长 | 较新 |
| 学习曲线 | 低 | 低 | 中(独特语法) |
包体积详细对比
Zod v3.23:
├── 完整包:~57KB (min)
└── 无法 tree-shake(class 实例方法都会被保留)
Valibot v1.0:
├── 完整包:~7KB (min)
├── 只用 string + email:~1.5KB
└── 函数式设计,按需引入
ArkType v2.0:
├── 完整包:~37KB (min)
└── 编译器在运行时做类型解析,基础开销较大二、API 对比
基本类型定义
// ========== Zod ==========
import { z } from 'zod';
const UserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
age: z.number().int().min(0),
});
type User = z.infer<typeof UserSchema>;
// ========== Valibot ==========
import * as v from 'valibot';
const UserSchema = v.object({
name: v.pipe(v.string(), v.minLength(1)),
email: v.pipe(v.string(), v.email()),
age: v.pipe(v.number(), v.integer(), v.minValue(0)),
});
type User = v.InferOutput<typeof UserSchema>;
// ========== ArkType ==========
import { type } from 'arktype';
const User = type({
name: 'string > 0',
email: 'string.email',
'age?': 'integer >= 0',
});
type User = typeof User.infer;联合类型和可辨识联合
// ========== Zod ==========
const Shape = z.discriminatedUnion('kind', [
z.object({ kind: z.literal('circle'), radius: z.number() }),
z.object({ kind: z.literal('rect'), width: z.number(), height: z.number() }),
]);
// ========== Valibot ==========
const Shape = v.variant('kind', [
v.object({ kind: v.literal('circle'), radius: v.number() }),
v.object({ kind: v.literal('rect'), width: v.number(), height: v.number() }),
]);
// ========== ArkType ==========
const Shape = type({
kind: "'circle'",
radius: 'number',
}).or({
kind: "'rect'",
width: 'number',
height: 'number',
});数据转换
// ========== Zod ==========
const NumberFromString = z.string()
.transform(val => parseInt(val, 10))
.pipe(z.number().min(0));
// ========== Valibot ==========
const NumberFromString = v.pipe(
v.string(),
v.transform(val => parseInt(val, 10)),
v.number(),
v.minValue(0),
);
// ========== ArkType ==========
const NumberFromString = type('string')
.pipe((s) => parseInt(s, 10))
.to('number >= 0');错误处理
// ========== Zod ==========
const result = UserSchema.safeParse(data);
if (!result.success) {
// result.error.issues: ZodIssue[]
// result.error.flatten(): { formErrors, fieldErrors }
console.log(result.error.flatten().fieldErrors);
}
// ========== Valibot ==========
const result = v.safeParse(UserSchema, data);
if (!result.success) {
// result.issues: SchemaIssue[]
const flat = v.flatten(result.issues);
console.log(flat); // { nested: { name: [...], email: [...] } }
}
// ========== ArkType ==========
const result = User(data);
if (result instanceof type.errors) {
// result.summary: string(人类可读)
console.log(result.summary);
// result.byPath: 按路径分组的错误
}三、性能对比
基准测试结果(参考值)
简单对象校验(10 个字段):
├── ArkType:最快(预编译类型检查)
├── Valibot:次快
└── Zod:最慢(但对大多数应用足够)
复杂嵌套对象:
├── ArkType:领先优势更明显
├── Valibot:稳定
└── Zod:差距拉大
结论:
- 大多数应用场景差异可忽略(微秒级)
- 高频热路径(如请求校验中间件)才需要关注性能
- ArkType 的性能优势来自编译期优化为什么 ArkType 更快?
ArkType 在定义 Schema 时就进行"编译"——将类型定义转换为优化后的校验函数。Zod 和 Valibot 在每次 parse 时都是解释执行。
四、生态集成对比
React Hook Form
// ========== Zod ==========
import { zodResolver } from '@hookform/resolvers/zod';
const form = useForm({ resolver: zodResolver(UserSchema) });
// ========== Valibot ==========
import { valibotResolver } from '@hookform/resolvers/valibot';
const form = useForm({ resolver: valibotResolver(UserSchema) });
// ========== ArkType ==========
// 目前无官方 resolver,需自定义tRPC
// ========== Zod ==========(原生支持)
const router = t.router({
createUser: t.procedure.input(UserSchema).mutation(/* ... */),
});
// ========== Valibot ==========(tRPC v11+ 支持)
import { wrap } from '@trpc/server';
const router = t.router({
createUser: t.procedure.input(wrap(UserSchema)).mutation(/* ... */),
});
// ========== ArkType ==========
// tRPC 适配器较少框架集成总览
| 框架/工具 | Zod | Valibot | ArkType |
|---|---|---|---|
| tRPC | ✅ 原生 | ✅ 适配器 | ⚠️ 社区 |
| React Hook Form | ✅ 官方 | ✅ 官方 | ❌ |
| Next.js Server Actions | ✅ | ✅ | ⚠️ |
| Nuxt | ✅ | ✅ | ⚠️ |
| Hono | ✅ | ✅ | ⚠️ |
| Drizzle ORM | ✅ | ✅ | ❌ |
| Conform | ✅ | ✅ | ❌ |
五、选型建议
选 Zod 的场景
- 需要最完善的生态集成(tRPC、React Hook Form、Next.js 等)
- 团队已经在使用 Zod,没有包体积压力
- 偏好链式 API 的开发体验
- 需要 branded types、递归类型等高级特性
选 Valibot 的场景
- 包体积敏感(移动端、嵌入式、Serverless Edge Functions)
- 需要最优 tree-shaking
- 偏好函数式编程风格
- 对 Zod API 熟悉(Valibot API 设计参考了 Zod)
选 ArkType 的场景
- 性能敏感的高频校验场景
- 偏好类型即 Schema 的声明式语法
- 项目不依赖 tRPC / RHF 等需要适配器的框架
- 愿意接受较新的库和较小的社区
决策流程图
包体积是首要考虑?
├── 是 → Valibot
└── 否 →
需要 tRPC / RHF 集成?
├── 是 → Zod
└── 否 →
性能是关键因素?
├── 是 → ArkType
└── 否 → Zod(生态最成熟)六、迁移指南
Zod → Valibot
// Zod
z.string().min(1).email()
// Valibot
v.pipe(v.string(), v.minLength(1), v.email())
// Zod
z.object({ name: z.string() }).partial()
// Valibot
v.partial(v.object({ name: v.string() }))
// Zod
z.infer<typeof Schema>
// Valibot
v.InferOutput<typeof Schema>
// Zod
schema.safeParse(data)
// Valibot
v.safeParse(schema, data) // 注意:schema 是第一个参数
// Zod
schema.parse(data)
// Valibot
v.parse(schema, data)核心差异记忆
Zod: schema.method(data) → 面向对象,方法在实例上
Valibot: method(schema, data) → 函数式,schema 作为参数传入
ArkType: type("syntax") → 类型字符串,接近 TS 语法面试高频题
1. Zod 和 Valibot 的核心区别是什么?
答案:最核心的区别是 API 设计范式——Zod 是面向对象的链式 API(z.string().min(1)),所有方法挂在 class 实例上,无法 tree-shake;Valibot 是函数式 API(v.pipe(v.string(), v.minLength(1))),每个校验器是独立函数,支持按需引入。这导致 Valibot 的包体积只有 Zod 的约 1/8。
2. 什么场景下包体积差异真的重要?
答案:Serverless Edge Functions(Cloudflare Workers 有 1MB 限制)、移动端 PWA(弱网环境首屏加载)、以及被大量项目依赖的基础库。普通 Web 应用中 50KB 的差异通常不是瓶颈,此时生态成熟度(Zod)或性能(ArkType)可能是更重要的考量。
3. ArkType 的类型字符串语法有什么优缺点?
答案:优点是非常接近 TypeScript 原生语法,学习成本低且代码更简洁(type({ age: 'number >= 0' }))。缺点是字符串中的类型表达式没有 IDE 补全和即时类型检查,出错时错误信息不如 Zod/Valibot 直观,且社区生态和框架集成还不够成熟。
4. 这三个库能混用吗?
答案:技术上可以在同一项目中使用多个校验库,但不推荐。它们的类型推断方式不同(z.infer vs v.InferOutput vs typeof schema.infer),混用会增加心智负担和包体积。如果要迁移,建议逐模块替换而非混用。