📌 项目地址:colinhacks/zod | ⭐ 43,618 颗星 | 🔧 TypeScript | 📜 未标注
📌 项目地址:colinhacks/zod | ⭐ 43,618 | 🔧 TypeScript
TypeScript 管不到的地方
TypeScript 的类型在编译后就没了。你给接口返回值标了 User 类型,后端实际返回什么,编译器管不着。as User 这种断言只是把报错推迟到生产环境。
传统做法是维护两份代码:一份 TypeScript interface,一份校验逻辑。改了一边忘另一边,是家常便饭。
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();
凡是数据从“外部”进入你代码的位置,都是它的地盘。
约束与组合
字符串链式加约束:
const schema = z
.string()
.min(5)
.max(10)
.regex(/^[a-z]+$/);
对象可以嵌套,字段支持 .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——对接需要 JSON Schema 的工具时有用。
和 Joi、Yup 比什么
核心区别就一条:TypeScript-first。Joi、Yup 诞生时 TypeScript 还没普及,类型推导是后补的;zod 从第一行代码起就把静态类型当目标,z.infer 的推导质量是主要竞争力。
README 还明确强调 zero dependencies。零依赖意味着供应链审计干净,装进 monorepo 不会拖进一堆传递依赖。对一个被 43k 项目引用的基础库来说,这点分量不轻。
上手前要知道的
parse失败抛ZodError,调用方要么 try/catch,要么用safeParse。选哪个取决于你想在哪一层处理错误。- zod 3 到 zod 4 的版本差异不小,看教程先对准自己用的版本。
- 它只做校验和类型推导,不做序列化,替代不了整个数据层。
我的判断:在 TypeScript 项目里处理外部输入,zod 已经是事实上的默认选项之一。先花半小时过一遍基础 API,再决定要不要全面采用,比直接上手写校验代码划算。