Zod 基础学习文档
1. 简介
Zod 是一个以 TypeScript 优先的模式声明和验证库。它的目标是:
- 消除重复类型定义:定义一次 Schema,自动获得 TypeScript 类型。
- 运行时校验:确保输入的数据符合预期的结构。
- 类型安全:与 TypeScript 完美集成。
官方文档连接: 👉 zod.dev
GitHub 仓库: 👉 github.com/colinhacks/…
2. 安装
在你的项目中运行以下命令:
npm install zod
# 或者
yarn add zod
# 或者
pnpm add zod
3. 基础数据类型校验
Zod 提供了一系列基础类型来校验原始数据。
字符串
import { z } from "zod";
// 定义:必须是字符串
const nameSchema = z.string();
// 校验
nameSchema.parse("tanya"); // ✅ 成功
// nameSchema.parse(12); // ❌ 抛出 ZodError
常用字符串验证链
const emailSchema = z.string().email();
const passwordSchema = z.string().min(8).max(20);
const urlSchema = z.string().url();
emailSchema.parse("zod@example.com"); // ✅
// emailSchema.parse("invalid-email"); // ❌
数字
const ageSchema = z.number().positive(); // 必须是正数
ageSchema.parse(18); // ✅
// ageSchema.parse(-5); // ❌
其他基础类型
z.boolean(); // 布尔值
z.date(); // 日期对象
z.undefined(); // undefined
z.null(); // null
z.void(); // undefined 或 null
z.any(); // 允许任何值
z.unknown(); // 允许任何值,但要求在使用前进行类型检查
4. 复杂对象
在开发中,我们最常处理的是对象。Zod 允许你像构建接口一样构建 Schema。
定义对象
// 定义一个用户对象的 Schema
const UserSchema = z.object({
username: z.string(),
age: z.number().optional(), // optional() 表示该字段可选
isAdmin: z.boolean().default(false), // default() 设置默认值
});
// ✅ 成功
UserSchema.parse({ username: "Alice" });
// ✅ 成功 (age 可以不传,因为它是 optional)
UserSchema.parse({ username: "Bob", age: 20 });
// ❌ 失败 (username 缺失)
// UserSchema.parse({ age: 20 });
组合与扩展
你可以像操作对象一样操作 Schema:
const BaseUser = z.object({ id: z.number() });
const LoggedInUser = BaseUser.extend({
username: z.string(),
});
// LoggedInUser 包含 id 和 username
5. 自动类型推断
这是 Zod 最强大的功能。你不需要维护两份代码(一份 Schema,一份 TypeScript interface)。
z.infer
使用 z.infer<typeof YourSchema> 来提取类型。
const UserSchema = z.object({
username: z.string(),
age: z.number(),
});
// ✅ 自动推导出 TypeScript 类型
type User = z.infer<typeof UserSchema>;
/*
上面的代码等同于:
type User = {
username: string;
age: number;
}
*/
// 现在你可以把这个类型用在函数参数中
function greet(user: User) {
console.log(`Hello, ${user.username}`);
}
6. 处理错误
当校验失败时,Zod 会抛出一个 ZodError 异常。你应该使用 try...catch 来捕获它并获取详细的错误信息。
const MySchema = z.object({
name: z.string().min(3),
email: z.string().email(),
});
try {
MySchema.parse({ name: "Jo", email: "not-an-email" });
} catch (err) {
if (err instanceof z.ZodError) {
console.log("验证失败!");
// err.errors 是一个包含所有具体错误的数组
console.log(err.errors);
/* 输出示例:
[
{
"code": "too_small",
"minimum": 3,
"type": "string",
"inclusive": true,
"message": "String must contain at least 3 character(s)",
"path": [ "name" ]
},
{
"code": "invalid_string",
"validation": "email",
"path": [ "email" ],
"message": "Invalid email"
}
]
*/
}
}
7. 安全解析
如果你不希望使用 try...catch,可以使用 .safeParse()。它返回一个包含成功状态的对象。
const result = MySchema.safeParse({ name: "Jo" });
if (result.success) {
console.log("数据有效:", result.data);
} else {
console.log("数据无效:", result.error);
}
8. 进阶:枚举与数组
枚举
const FruitsEnum = z.enum(["apple", "banana", "orange"]);
FruitsEnum.parse("apple"); // ✅
// FruitsEnum.parse("pear"); // ❌
数组
// 字符串数组
const stringArray = z.array(z.string());
stringArray.parse(["apple", "banana"]); // ✅
// stringArray.parse(["apple", 123]); // ❌
9. 实战场景:API 响应校验
假设你调用了一个 API,返回的数据结构如下。你希望在 TypeScript 中使用它,但又不完全信任后端返回的数据。
import { z } from "zod";
// 1. 定义 Schema
const ApiResponseSchema = z.object({
id: z.number(),
title: z.string(),
completed: z.boolean(),
});
async function fetchTodo(id: number) {
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
const rawData = await response.json();
// 2. 校验数据
const result = ApiResponseSchema.safeParse(rawData);
if (!result.success) {
console.error("API 返回的数据结构不正确!", result.error);
throw new Error("Invalid Data");
}
// 3. 此时 TypeScript 知道 result.data 是符合类型定义的
// 你可以安全地访问 .title, .id 等
console.log(`标题: ${result.data.title}`);
}
fetchTodo(1);
总结
- 定义 Schema:使用
z.string(),z.object()等构建规则。 - 校验:使用
.parse()(抛出异常) 或.safeParse()(返回对象)。 - 获取类型:使用
z.infer<>将 Schema 转换为 TypeScript 类型。