zod库-以 TypeScript 优先的模式声明和验证库

9 阅读2分钟

Zod 基础学习文档

1. 简介

Zod 是一个以 TypeScript 优先的模式声明和验证库。它的目标是:

  1. 消除重复类型定义:定义一次 Schema,自动获得 TypeScript 类型。
  2. 运行时校验:确保输入的数据符合预期的结构。
  3. 类型安全:与 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);

总结

  1. 定义 Schema:使用 z.string(), z.object() 等构建规则。
  2. 校验:使用 .parse() (抛出异常) 或 .safeParse() (返回对象)。
  3. 获取类型:使用 z.infer<> 将 Schema 转换为 TypeScript 类型。