先看一个真实 bug
interface User { id: string; name: string; age: number; } async function loadUser(id: string) { const res = await fetch(`/api/users/${id}`); const user = await res.json() as User; // ← 谎言 return user; } const u = await loadUser("1"); console.log(u.age.toFixed(0)); // TypeError: u.age.toFixed is not a function // 服务端返回 { id, name, age: "25" }(字符串!),TS 根本不知道
TS 的 as User 是断言,不是校验——它只是让编译器闭嘴,并不检查数据真长这样。
TS 管不到的三个场景
HTTP / API 返回
后端改了字段、字段类型变了、nullable 没标——前端 TS 完全蒙在鼓里。
表单 / 用户输入
HTML input 永远返回 string,
age: 18 实际是 "18"。还有恶意输入 / 空值 / 超长。localStorage / cookie / URL 参数
JSON.parse(localStorage.getItem("user")!) 返回 any——schema 可能是三个月前版本存进去的。第三方 SDK / LLM 输出
GPT 的 JSON 模式也会偶尔返回
"score": "0.9" 而不是 number。手写校验的痛
function isUser(x: unknown): x is User { if (typeof x !== "object" || x === null) return false; const o = x as any; return typeof o.id === "string" && typeof o.name === "string" && typeof o.age === "number"; }
问题:
- 字段多起来代码爆炸(20 字段写 60 行判断)
- 没错误信息(只知道 false,不知道哪儿错)
- 类型和校验分两处定义,改一个忘一个
- 嵌套对象、数组、union 手写很烦
Zod 的核心想法
import { z } from "zod"; const User = z.object({ id: z.string(), name: z.string().min(1), age: z.number().int().min(0), }); type User = z.infer<typeof User>; // { id: string; name: string; age: number } const u = User.parse(await res.json()); // u 类型是 User,且数据真的是 User——否则 parse 抛异常
一份 schema,三个收益:
- 运行时校验——不对就抛错
- TS 类型自动推断——不用重复写 interface
- 错误信息——哪个字段错、为什么错,结构化返回
校验库对比
| Zod | Yup | Joi | io-ts | Valibot | |
|---|---|---|---|---|---|
| TS 优先 | ✓ | 后加的 | 后加的 | ✓ | ✓ |
| 类型推断 z.infer | ✓ | 部分 | 差 | ✓(啰嗦) | ✓ |
| 错误信息 | 结构化 | 字符串 | 结构化 | 函数式 | 结构化 |
| bundle 大小 | ~12KB(v4) | ~30KB | ~150KB | ~15KB | ~2KB |
| 生态 | 极广 | 较广 | Node 为主 | 学术派 | 新 |
| 学习曲线 | 低 | 低 | 中 | 高(需 fp-ts) | 低 |
为什么 Zod 赢了
Yup 比它老,但 TS 类型推断是后期打补丁;io-ts 类型最严格,但
Yup 比它老,但 TS 类型推断是后期打补丁;io-ts 类型最严格,但
fp-ts 函数式 API 大部分前端人接受不了;Joi 是 Node 时代产物,bundle 大。Zod 拿捏了"TS 友好 + API 像 Yup 简单 + 类型精准"三角平衡。
Valibot 的崛起
Valibot 是 2024 起的"挑战者"——核心思想类似 Zod,但用函数式组合:
// Zod z.string().email().min(5); // Valibot pipe(string(), email(), minLength(5));
Valibot 的 bundle 小(tree-shakable 到 ~2KB),但 Zod v4 bundle 大幅压缩后差距没那么大——生态还是 Zod 压倒性。
Zod v4 为什么重要
2024 年 Colin McDonnell(Zod 作者)宣布 v4 从零重写——2025 发布,2026 稳定:
- parse 速度提升 14 倍——对大型对象 / 批量解析场景效果最明显
- bundle 从 ~45KB 降到 ~12KB——tree-shaking 更友好
- 新 z.iso.datetime 等语义化 API
- 更好的 discriminatedUnion 类型推断
- 新的错误接口
issues——和旧版兼容但更规整
v3 vs v4
本教程以 Zod v4 为主(2026 生态主流)。大多数 API 和 v3 兼容,迁移只需更新少量类型签名——第 9 章详细讲迁移。
本教程以 Zod v4 为主(2026 生态主流)。大多数 API 和 v3 兼容,迁移只需更新少量类型签名——第 9 章详细讲迁移。
Zod 不适合的场景
- 超简单的校验——"只是检查字符串非空",手写
if (!x)更直接 - cascading updates——Zod 是一次性解析,不适合实时响应式(用 Jazz/SolidJS signals)
- 超大对象 100k+ 字段——parse 开销不可忽略,考虑抽样校验
- 无类型语言——Zod 为 TS 设计,纯 JS 项目能用但没推断价值
生态地位
- React Hook Form:
@hookform/resolvers/zod官方推荐 - tRPC:input/output schema 原生 Zod
- Next.js Server Actions:社区最常用 Conform + Zod
- OpenAI Structured Output:SDK 直接吃 Zod schema
- Drizzle ORM:
drizzle-zod从表结构生成 schema - tRPC / Hono / Elysia:全部第一类支持
学会 Zod 基本就拿到了 TS 生态的通票。
一个真实例子:环境变量校验
// env.ts import { z } from "zod"; const envSchema = z.object({ DATABASE_URL: z.string().url(), PORT: z.coerce.number().int().min(1).max(65535), NODE_ENV: z.enum(["development", "production", "test"]), STRIPE_SECRET: z.string().startsWith("sk_"), }); export const env = envSchema.parse(process.env); // 启动即验证:NODE_ENV=foobar 直接崩,带行号错误信息 // 比运行到第 20 秒才发现 config 错了友好多了
这就是 Zod 的"防御式编程"风格——在系统边界(HTTP / env / 文件 / DB 输出)设校验卡点,内部代码可以放心假设类型正确。
本章小结
- TS 类型在运行时不存在——HTTP/表单/存储等边界需要运行时校验
- 手写 isXxx 校验随字段爆炸、错误信息差、类型和校验分离
- Zod 一份 schema 同时提供 校验 + TS 类型推断 + 结构化错误
- vs Yup/Joi/io-ts/Valibot:Zod 在 TS 友好 + 简单 API + 生态之间最平衡
- Zod v4 parse 快 14 倍、bundle 小 3 倍,是 2026 主流版本