📌 项目地址:colinhacks/zod | ⭐ 43,618 颗星 | 🔧 TypeScript | 📜 未标注
📌 项目地址:colinhacks/zod | ⭐ 43,618 | 🔧 TypeScript
问题在哪
TypeScript 的类型在编译后就不存在了。你给接口返回值标了 User 类型,但后端实际返回什么,TypeScript 管不着。as User 这种断言只是把问题往后推,真出错是在生产环境炸的。
传统做法是维护两份代码:一份 TypeScript interface,一份校验逻辑(手写 if,或者 Joi/Yup 的 schema)。改了一边忘另一边,是家常便饭。
zod 的思路:只写一份 schema,运行时校验和静态类型都从它推导出来。43,618 颗 star 说明这条路确实是社区想要的。
基本用法
先看最小例子:
const User = z.object({
username: z.string(),
});
User.parse({ username: "Ludwig" });
// extract the inferred type
type User = z.infer<typeof User>;
// { username: string }
关键在 z.infer。类型是从 schema 推导的,schema 改了类型自动跟着变,不存在两份代码不同步的问题。这是整个库的核心机制。
处理不可信输入
parse 校验失败会抛 ZodError。如果不想用 try/catch,README 提供了 safeParse——失败不抛异常,返回错误信息:
const processData = (data: unknown) => {
const parsed = SafeData.safeParse(data);
if (!parsed.success) {
throw new Error("Bad data");
}
}
典型场景是从环境变量、API 请求体这类外部输入中提取类型安全的数据。比如校验 UUID:
const idSchema = z.string().uuid();
约束和组合
字符串可以链式加约束,README 的例子:
const schema = z
.string()
.min(5)
.max(10)
.regex(/^[a-z]+$/);
对象 schema 也可以嵌套组合,字段支持 .optional():
const propertySchema = z.object({
default: z.string().optional(),
description: z.string().optional(),
enum: z.array(z.string()).optional(),
type: z.string(),
});
生态上,README 提到官方支持的 zod-to-json-schema,可以把 zod schema 转成 JSON Schema。
和 Joi、Yup 比呢
核心区别在名字里:TypeScript-first。Joi、Yup 诞生时 TypeScript 还不普及,类型推导是后来补的;zod 从第一天就把静态类型当一等公民,z.infer 的推导质量是主要竞争力。
README 明确强调的另一点:zero dependencies。零依赖对供应链审计敏感的项目很友好,装进 monorepo 也不有一堆传递依赖。
用之前要知道的
parse失败抛ZodError,调用方要么 try/catch,要么改用safeParse。- zod 3 到 zod 4 演进过程中版本差异不小,看教程时先对准自己用的版本,迁移事项参考官方文档。
- 它只做校验和类型推导,不做序列化,也别指望它替代整个数据层。
我的判断:在 TypeScript 项目里处理外部输入(HTTP 请求、环境变量、配置文件、第三方 API),zod 目前是事实上的默认选项之一。先花半小时学它,再决定要不要用,比直接上手写校验代码划算。