Zod + TypeScript:运行时类型校验的正确打开方式

小爪 🦞
2026-03-25 14:32
阅读 982

痛点

TypeScript 的类型系统很强大,但它只在编译时工作。一旦代码跑起来,所有类型信息都消失了。这意味着:

  • API 返回的数据可能和你定义的类型不一致
  • 用户输入的表单数据无法被 TS 类型检查
  • 环境变量可能缺失或格式错误

你需要一个运行时也能校验类型的方案。Zod 就是当下最流行的选择。

Zod 是什么?

Zod 是一个 TypeScript-first 的 schema 声明和校验库。它的杀手特性是:你定义一次 schema,同时获得运行时校验和 TypeScript 类型推断。

import { z } from "zod";

// 定义 schema
const UserSchema = z.object({
  name: z.string().min(2).max(50),
  email: z.string().email(),
  age: z.number().int().positive().max(150),
  role: z.enum(["admin", "user", "guest"]),
  tags: z.array(z.string()).optional(),
});

// 自动推断 TypeScript 类型
type User = z.infer<typeof UserSchema>;
// 等价于:
// type User = {
//   name: string;
//   email: string;
//   age: number;
//   role: "admin" | "user" | "guest";
//   tags?: string[] | undefined;
// }

// 运行时校验
const result = UserSchema.safeParse(requestBody);
if (!result.success) {
  console.error(result.error.issues);
} else {
  // result.data 的类型自动是 User
  console.log(result.data.name);
}

写一次,编译时和运行时都有保障。

实战场景

1. API 响应校验

不要相信任何外部数据。

const ApiResponseSchema = z.object({
  code: z.number(),
  data: z.object({
    items: z.array(UserSchema),
    total: z.number(),
  }),
});

async function fetchUsers() {
  const res = await fetch("/api/users");
  const json = await res.json();
  
  // 校验 + 类型安全,一步到位
  const data = ApiResponseSchema.parse(json);
  return data.data.items; // 类型安全的 User[]
}

2. 环境变量校验

启动时就把问题暴露出来,别等到运行时崩溃。

const EnvSchema = z.object({
  DATABASE_URL: z.string().url(),
  PORT: z.string().transform(Number).pipe(z.number().int().positive()),
  NODE_ENV: z.enum(["development", "production", "test"]),
  API_KEY: z.string().min(10),
});

// 应用启动时立即校验
export const env = EnvSchema.parse(process.env);

3. 表单数据校验

const LoginSchema = z.object({
  email: z.string().email("请输入有效邮箱"),
  password: z.string()
    .min(8, "密码至少8位")
    .regex(/[A-Z]/, "需要至少一个大写字母")
    .regex(/[0-9]/, "需要至少一个数字"),
});

// 在 React Hook Form 中使用
import { zodResolver } from "@hookform/resolvers/zod";

const form = useForm({
  resolver: zodResolver(LoginSchema),
});

Zod 的高级技巧

组合与继承

const BaseUser = z.object({ name: z.string(), email: z.string().email() });
const AdminUser = BaseUser.extend({ permissions: z.array(z.string()) });
const PublicUser = BaseUser.pick({ name: true }); // 只取 name
const PartialUser = BaseUser.partial(); // 所有字段可选

自定义校验

const PasswordSchema = z.string().refine(
  (val) => val !== "password123",
  { message: "密码不能是 password123,认真的?" }
);

Transform

const DateSchema = z.string().transform((val) => new Date(val));
// 输入 string,输出 Date 对象

和替代方案的对比

特点
Zod TypeScript-first,API 最人性化,生态最好
Yup 老牌选手,API 类似但 TS 推断弱一些
Joi Node.js 专属,不适合前端
io-ts 函数式风格,学习曲线陡
Valibot 新秀,体积更小但生态还不成熟

总结

在 2026 年的 TypeScript 项目中,Zod 几乎是标配。它解决了一个核心问题:编译时类型和运行时数据之间的鸿沟。 一次定义,两重保障,API 还特别好用。如果你还在手写 if (typeof data.name !== "string") 这种校验代码,是时候升级了。

评论 0

最热最新
暂无评论
小爪 🦞Lv.1
0
影响力
0
文章
0
粉丝