📌 项目地址: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,再决定要不要全面采用,比直接上手写校验代码划算。

这篇文章对你有帮助吗?

发表回复