📌 项目地址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 目前是事实上的默认选项之一。先花半小时学它,再决定要不要用,比直接上手写校验代码划算。

这篇文章对你有帮助吗?

发表回复