news 2026/2/28 14:07:59

Zod:TypeScript 类型守卫与数据验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Zod:TypeScript 类型守卫与数据验证

我见过许多因为运行时数据不匹配而导致的崩溃,也曾写过无数防御性代码和any断言,哈哈 😄。TypeScript 的类型安全本来就不该止步于编译期。直到遇见 Zod,Zod 不仅是一个验证库,它为 TypeScript 带来运行时安全,是目前最优雅、最彻底的解决方案。

我们为何需要 Zod?

TypeScript 最让人上瘾的地方在于编译时类型检查,但这也是它的最大谎言,因为类型在运行时彻底消失,你需要小心小心再小心,使用 TypeScript 并不代表类型安全。

/* by 01130.hk - online tools website : 01130.hk/zh/px2rem.html */ interface User { name: string; age: number; role: 'admin' | 'user'; } fetch('/api/user').then(res => res.json()).then((data: User) => { // 编译通过,但如果 data.role 返回的是 "administrator",运行会报错 console.log(data.role.toUpperCase()); });

而 Zod 的答案是:只定义一次 Schema,既得到运行时验证,又得到完美的 TypeScript 类型。

/* by 01130.hk - online tools website : 01130.hk/zh/px2rem.html */ const UserSchema = z.object({ name: z.string(), age: z.number(), role: z.enum(['admin', 'user']), }); type User = z.infer<typeof UserSchema>;

通过 Schema 推断出完美类型 User,无需二次声明。这样, UserSchema 用于运行时验证,User 用于类型推断。

Zod 入门

npm install zod
import { z } from 'zod'; // 基础类型 const StringSchema = z.string(); const NumberSchema = z.number().int().positive();

核心 API:parse 和 safeParse,我建议优先使用.safeParse()

const schema = z.string(); try { const result = schema.parse(123); console.log(result); } catch (error) { console.error('验证失败:', error.errors); }

parse 抛出 ZodError, 你需要通过try catch捕获错误,否则导致程序崩溃。

const schema = z.string(); const result = schema.safeParse(123); if (result.success) { console.log('验证成功:', result.data); } else { console.log('验证失败:', result.error.errors); }

safeParse 返回{ success: true, data: T }{ success: false, error: ZodError },你可以通过 success 判断是否验证成功,然后通过 data 获取验证后的数据,或者通过error.errors获取错误信息, 这样你可以优雅地处理错误。

构建复杂数据模型

const AddressSchema = z.object({ street: z.string(), city: z.string(), zipCode: z.string().regex(/^\d{5}$/), }); const UserSchema = z.object({ id: z.string().uuid(), name: z.string().min(2).max(50), email: z.string().email(), age: z.number().int().min(13).max(120), address: AddressSchema.optional(), // 可选嵌套对象 tags: z.array(z.string()).default([]), // 默认值 role: z.enum(['admin', 'user', 'moderator']), status: z.enum(['active', 'inactive']).default('active'), });

组合技巧,这些是我最常用的:

.extend()是 Zod 最被低估的特性之一。它让你能以面向对象的方式构建 Schema 体系,比 interface 继承更安全,因为运行时验证也会继承。

// 扩展 const AdminSchema = UserSchema.extend({ permissions: z.array(z.string()), });

比较常见的是,实现查询接口的分页查询 Schema,分页查询 Schema 包含 page 和 pageSize 字段,自其他 Schema 可以继承分页查询 Schema 并添加其他字段。

const PageSchema = z.object({ page: z.number().min(1).default(1), pageSize: z.number().min(1).max(100).default(10), }); const UserPageSchema = PageSchema.extend({ name: z.string().min(2).max(50), }); type UserPage = z.infer<typeof UserPageSchema>;

合并, 优先级后者覆盖前者。

const MergedSchema = UserSchema.merge(z.object({ role: z.literal('admin'), // 强制覆盖 }));

交集。

const IntersectionSchema = z.intersection(UserSchema, z.object({ isVerified: z.boolean(), }));

进阶模式与精细校验

可辨识联合(Discriminated Union),比如我们用它来处理 Redux Action。

可辨识联合(Discriminated Union),也称为标签联合(Tagged Union)或代数数据类型(Algebraic Data Type),是一种高级类型系统特性,用于表示可能是多种不同类型之一的值。

const ActionSchema = z.discriminatedUnion('type', [ z.object({ type: z.literal('INCREMENT'), payload: z.number() }), z.object({ type: z.literal('DECREMENT'), payload: z.number() }), z.object({ type: z.literal('SET_USER'), payload: UserSchema }), ]); type Action = z.infer<typeof ActionSchema>;

z.discriminatedUnion比手动写.or()更清晰,TypeScript 窄化(narrowing)也更完美。

字符串高级校验

z.string() .min(8, '至少8位') .regex(/[A-Z]/, '必须含大写字母') .regex(/[a-z]/, '必须含小写字母') .regex(/[0-9]/, '必须含数字') .regex(/[^A-Za-z0-9]/, '必须含特殊字符');

通过 Transform 验证后自动转换,这太棒了,比如写 restful 接口时,我们希望 Query id 是 number 类型,但是传入 string 类型,我们可以借助 Transform 自动转换为 number 类型。

const IdSchema = z.string().transform(str => parseInt(str));

推断 TypeScript 类型

修改 Schema,类型自动更新,无需手动更新类型,完美同步。

type User = z.infer<typeof UserSchema>;

与 TypeScript 原生 enum 互操作。

enum Role { Admin = 'admin', User = 'user', } const RoleSchema = z.nativeEnum(Role);

常见应用场景

API 响应验证

async function apiFetch<T extends z.ZodType>( url: string, schema: T ): Promise<z.infer<T>> { const res = await fetch(url); const data = await res.json(); const result = schema.safeParse(data); if (!result.success) { throw new Error(`API验证失败: ${result.error.message}`); } return result.data; } // 使用 const user = await apiFetch('/api/user', UserSchema);

表单验证

与 React-Hook-Form 完美结合,类型安全 + 错误信息自动同步。AI 非常喜欢这套方案,AI 生成的代码非常不容易出错。

const formSchema = z.object({ email: z.string().email('邮箱格式错误'), password: z.string().min(8, '密码至少8位'), confirm: z.string(), }).refine(data => data.password === data.confirm, { message: "两次密码不一致", path: ["confirm"], }); type FormData = z.infer<typeof formSchema>; const { register, handleSubmit, formState: { errors } } = useForm<FormData>({ resolver: zodResolver(formSchema), });

但是,我们最常用的 antd 的 Form 组件,与 Zod 结合并不完美 #40580。表单一旦复杂,字段之间的关联性就更多了,如果都在 jsx 中处理,代码的可读性、可维护性就大大降低,如果把字段的定义以及验证单独提取出来,形成业务实体对应的实体逻辑,无疑更好。

社区有人为此实现了一个 antd-zod 库,是目前我比较推荐的方案。

import { createSchemaFieldRule } from 'antd-zod'; const CustomFormValidationSchema = z.object({ fieldString: z.string(), fieldNumber: z.number(), }); const rule = createSchemaFieldRule(CustomFormValidationSchema); export function SimpleForm() { return ( <Form> <Form.Item label="String field" name="fieldString" rules={[rule]}> <Input/> </Form.Item> <Form.Item label="Number field" name="fieldNumber" rules={[rule]}> <InputNumber/> </Form.Item> <Button htmlType="submit">Submit</Button> </Form> ); };

环境变量验证

const envSchema = z.object({ DATABASE_URL: z.string().url(), NODE_ENV: z.enum(['development', 'production', 'test']), JWT_SECRET: z.string().min(32), }); type Env = z.infer<typeof envSchema>; export const env = envSchema.parse(process.env);

对于环境变量校验我推荐你使用 t3-env,使用无效的环境变量部署应用程序是一件麻烦事。这个包可以帮助你避免这种情况。它支持使用任何 Standard Schema 兼容验证器,当然包括 Zod。

定义环境变量:

// src/env.mjs import { createEnv } from "@t3-oss/env-nextjs"; // or core package import { z } from "zod"; export const env = createEnv({ /* * Serverside Environment variables, not available on the client. * Will throw if you access these variables on the client. */ server: { DATABASE_URL: z.string().url(), OPEN_AI_API_KEY: z.string().min(1), }, /* * Environment variables available on the client (and server). * * 💡 You'll get type errors if these are not prefixed with NEXT_PUBLIC_. */ client: { NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1), }, /* * Due to how Next.js bundles environment variables on Edge and Client, * we need to manually destructure them to make sure all are included in bundle. * * 💡 You'll get type errors if not all variables from `server` & `client` are included here. */ runtimeEnv: { DATABASE_URL: process.env.DATABASE_URL, OPEN_AI_API_KEY: process.env.OPEN_AI_API_KEY, NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, }, });

使用时具有自动完成和类型推断功能

import { env } from "../env.mjs"; export const GET = (req: Request) => { const DATABASE_URL = env.DATABASE_URL; // use it... };

服务端应用:tRPC 示例

tRPC 是一个端到端类型安全的服务端框架。tRPC 的核心魔力就在于 Zod。输入验证、类型推断、自动生成客户端类型,全程零配置。

服务端实现文章发布接口,使用 Zod 验证输入

前端类型推断

端到端类型安全对于 AI 自动补全和提升生成代码质量也是非常有帮助的

AI 生成结构化数据

许多语言模型都能够生成结构化数据,通常定义为使用“JSON modes”或“tools”。然而,您需要手动提供模式,然后验证生成的数据,因为 LLM 可能会产生错误或不完整的结构化数据。

Vercel AI SDK 通过在generateText上使用output属性,标准化了模型提供商之间的结构化对象生成。 和streamText。您可以使用 Zod schemas,Valibot 或 JSON schemas 来指定您想要的数据结构,AI 模型将生成符合该结构的数据。

使用generateTextOutput.object()从提示中生成结构化数据。该模式还用于验证生成的数据,确保类型安全和正确性。

import { generateText, Output } from 'ai'; import { deepseek } from "@ai-sdk/deepseek"; import { z } from 'zod'; const { output } = await generateText({ model: deepseek("deepseek-v3.1"), output: Output.object({ schema: z.object({ recipe: z.object({ name: z.string(), ingredients: z.array( z.object({ name: z.string(), amount: z.string() }), ), steps: z.array(z.string()), }), }), }), prompt: '生成一份扬州炒饭食谱', });

与其他验证库对比

TypeScript 支持类型推断体积学习成本生态推荐场景
Zod原生一流完美~6KB极强所有 TS 项目(强烈推荐)
Yup需 cast~20KB老项目、JS 项目
JoiNode.js 后端
io-ts好(FP 风格)喜欢函数式编程的团队
AJV小(快)纯 JSON 验证场景

总结

这才是 TypeScript 应该有的样子。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/28 3:26:05

救命神器8个AI论文网站,本科生毕业论文轻松搞定!

救命神器8个AI论文网站&#xff0c;本科生毕业论文轻松搞定&#xff01; AI 工具如何成为论文写作的“救星”&#xff1f; 在如今的学术环境中&#xff0c;越来越多的本科生开始依赖 AI 工具来提升论文写作效率。无论是降低 AIGC 率&#xff0c;还是保持语义通顺&#xff0c;这…

作者头像 李华
网站建设 2026/2/28 1:16:26

旅行社工作大减负!

旅游旺季一到&#xff0c;旅行社忙得脚不沾地&#xff1f;收集旅客证件、核对信息、规划行程&#xff0c;每一项都让人头大&#xff01;别慌&#xff0c;现在有了 “神助攻”—— 护照阅读器&#xff0c;直接让旅行社工作效率拉满&#xff0c;轻松应对各种难题&#xff01;旅行…

作者头像 李华
网站建设 2026/2/11 8:40:36

【C# 12顶级语句深度解析】:掌握现代C#编程的终极利器

第一章&#xff1a;C# 12顶级语句概述C# 12 引入的顶级语句&#xff08;Top-Level Statements&#xff09;极大简化了应用程序的入口点定义&#xff0c;使开发者能够以更简洁的方式编写控制台或小型项目程序&#xff0c;无需手动创建类和 Main 方法。这一特性特别适用于学习、原…

作者头像 李华
网站建设 2026/2/27 19:04:05

解锁本科论文新境界:书匠策AI——你的学术隐形导航仪

在本科学习的尾声&#xff0c;面对毕业论文这座“大山”&#xff0c;许多同学常常感到力不从心。选题迷茫、逻辑混乱、表达不专业、格式调整繁琐……这些问题像一道道难以跨越的坎&#xff0c;让原本就紧张的学业生活更加雪上加霜。然而&#xff0c;在科技日新月异的今天&#…

作者头像 李华
网站建设 2026/2/27 10:12:41

本科毕业季不再“从零写起”:一位理工科学生的AI协作手记——那些论文写作中被忽略的隐形效率杠杆

又到一年毕业季。图书馆的灯亮得更早&#xff0c;咖啡杯在桌上堆成小山&#xff0c;凌晨三点的寝室键盘声此起彼伏。作为刚刚完成本科毕业论文的“过来人”&#xff0c;我深知那种面对空白文档的窒息感——不是没想法&#xff0c;而是不知道如何把零散的思路变成一篇结构严谨、…

作者头像 李华