类型安全的表单与 API 响应契约
使用 Zod 从架构推断 TypeScript 类型,并验证表单数据和 API 响应。
类型安全的表单与 API 响应契约 是 CoddyKit 上的免费 React Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 React Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 React Academy 课程共包含 4 节课。
类型安全的数据结构为何重要
表单和 API 响应是数据从不可信来源进入应用的边界。Zod 让您可以定义既能在运行时验证、又能推断 TypeScript 类型的模式。
Zod 模式基础
使用 Zod 的链式 API 定义模式。使用 z.infer<typeof schema> 提取 TypeScript 类型。
import { z } from 'zod';
const UserSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1).max(100),
email: z.string().email(),
age: z.number().int().min(0).max(150).optional(),
role: z.enum(['admin', 'user', 'guest']),
});
type User = z.infer<typeof UserSchema>;
// { id: string; name: string; email: string; age?: number; role: 'admin'|'user'|'guest' }使用 Zod 解析器的 React Hook Form
将 Zod 模式与 React Hook Form 集成,以实现类型安全的表单验证——TypeScript 知道表单数据和错误的结构。
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
const LoginSchema = z.object({
email: z.string().email('Invalid email'),
password: z.string().min(8, 'At least 8 characters'),
});
type LoginData = z.infer<typeof LoginSchema>;
function LoginForm() {
const { register, handleSubmit, formState: { errors } } = useForm<LoginData>({
resolver: zodResolver(LoginSchema),
});
const onSubmit = (data: LoginData) => {
// data is LoginData — fully typed, already validated
};
}验证 API 响应
使用 Zod 解析 API 响应,在边界处捕获结构不匹配问题——如果 API 返回了意外数据,您会得到详细错误,而不是等到组件深处发生运行时崩溃。
async function fetchUser(id: string): Promise<User> {
const res = await fetch(`/api/users/${id}`);
const json = await res.json();
return UserSchema.parse(json); // throws ZodError if shape is wrong
}使用安全解析优雅地处理错误
使用 schema.safeParse() 获取结果对象,而不是抛出错误——这非常适合需要向用户显示错误的表单验证场景。
const result = UserSchema.safeParse(formData);
if (!result.success) {
const errors = result.error.flatten().fieldErrors;
// { name: ['Must be at least 1 character'], email: ['Invalid email'] }
return errors;
}
const user = result.data; // User — fully typed使用 Zod 的判别联合
使用 z.discriminatedUnion 对 API 响应进行建模,使其根据成功或错误标志具有不同的结构。
const ApiResponse = z.discriminatedUnion('ok', [
z.object({ ok: z.literal(true), data: UserSchema }),
z.object({ ok: z.literal(false), error: z.string(), code: z.number() }),
]);
type ApiResult = z.infer<typeof ApiResponse>;
// { ok: true; data: User } | { ok: false; error: string; code: number }Zod 转换
使用 .transform() 在解析期间强制转换或重新组织数据,例如将日期字符串转换为 Date 对象。
const DateSchema = z.string().transform(s => new Date(s));
// Input: '2024-01-15' → Output: Date object
const EventSchema = z.object({
title: z.string(),
date: z.string().pipe(z.coerce.date()),
});
type Event = z.infer<typeof EventSchema>;
// { title: string; date: Date }复用模式
从基础模式派生创建和更新模式,以避免重复。
const UserSchema = z.object({ name: z.string(), email: z.string().email() });
// For creation: add password
const CreateUserSchema = UserSchema.extend({ password: z.string().min(8) });
// For update: all fields optional
const UpdateUserSchema = UserSchema.partial();
type CreateUser = z.infer<typeof CreateUserSchema>;
type UpdateUser = z.infer<typeof UpdateUserSchema>;使用 Zod 的服务器操作
在访问数据库之前,使用 Zod 在 Next.js 服务器操作中验证表单数据。
async function createPost(formData: FormData) {
'use server';
const schema = z.object({ title: z.string().min(3), body: z.string().min(10) });
const result = schema.safeParse(Object.fromEntries(formData));
if (!result.success) return { errors: result.error.flatten().fieldErrors };
await db.post.create({ data: result.data });
revalidatePath('/blog');
redirect('/blog');
}前端与后端之间共享模式
从共享包中导出 Zod 模式,让前端和后端使用相同的验证逻辑,使一个模式成为唯一可信来源。
// packages/schemas/src/user.ts
export const CreateUserSchema = z.object({ ... });
export type CreateUser = z.infer<typeof CreateUserSchema>;
// Frontend imports:
import { CreateUserSchema, CreateUser } from '@company/schemas';
// Backend imports:
import { CreateUserSchema } from '@company/schemas';Zod 错误消息
为每个字段自定义错误消息,以改善用户体验;Zod 会通过解析器自动将这些消息传递给表单的错误显示组件。
const RegistrationSchema = z.object({
username: z.string()
.min(3, 'Username must be at least 3 characters')
.max(20, 'Username cannot exceed 20 characters')
.regex(/^[a-z0-9_]+$/, 'Only lowercase letters, numbers, and underscores'),
});快速检查
z.infer<typeof MySchema> 在 TypeScript 中提供什么?
回顾
只定义一次 Zod 模式,并使用 z.infer 派生 TypeScript 类型。在 React Hook Form 中使用 zodResolver 进行类型安全的验证。在边界处使用 Zod 解析 API 响应,使用 safeParse 处理面向用户的错误,并通过共享包在前端和后端之间共享模式。
常见问题解答
「类型安全的表单与 API 响应契约」课时是免费的吗?
是的 — 「类型安全的表单与 API 响应契约」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 React Academy 课程的其余内容,请升级到 CoddyKit PRO。 React Academy 课程共包含 4 节课。
「类型安全的表单与 API 响应契约」这节课中我会学到什么?
使用 Zod 从架构推断 TypeScript 类型,并验证表单数据和 API 响应。 你通过在浏览器中直接运行的动手代码来练习 React Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 React Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 React Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「类型安全的表单与 API 响应契约」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 React Academy 课中编写并运行代码吗?
能。每节 React Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- 组件变体的可辨识联合类型
- React 中的条件类型与映射类型
- 带有 'as' 属性的多态组件
- 类型安全的表单与 API 响应契约