关于 react-hook-form-zod
面向 React 表单开发者的 React Hook Form 与 Zod 指南,提供类型化验证模式、示例模板和故障排查资料,涵盖客户端与服务端验证。
主要功能
- 01React Hook Form 与 Zod 配置
- 02基于 Schema 的类型推断
- 03动态及多步骤表单模式
- 04异步与服务端验证
- 05无障碍与性能指导
- 06模板和故障排查资料
适用场景
- 01实现登录或注册表单
- 02添加动态字段或多步骤表单
- 03在客户端和服务端验证输入
- 04排查 Resolver 与类型错误
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ secondsky/ claude-skills --skill react-hook-form-zod安装后在 Claude Code 中输入/react-hook-form-zod确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/accessibility.mderror-handling.mdlinks-to-official-docs.mdperformance-optimization.mdrhf-api-reference.mdshadcn-integration.mdtop-errors.mdzod-schemas-guide.mdscripts/check-versions.shtemplates/advanced-form.tsxasync-validation.tsxbasic-form.tsxcustom-error-display.tsxdynamic-fields.tsxmulti-step-form.tsxpackage.jsonserver-validation.tsxshadcn-form.tsx详细介绍
React Hook Form + Zod 验证
状态:已可用于生产环境 ✅ 最后更新:2025-11-21 依赖项:无(独立运行) 最新版本:[email protected]、[email protected]、@hookform/[email protected]
快速开始(10 分钟)
1. 安装软件包
bun add [email protected] [email protected] @hookform/[email protected]
为什么需要这些软件包:
- react-hook-form:高性能、灵活的表单,最大限度减少重新渲染
- zod:以 TypeScript 为先的架构验证,支持类型推断
- @hookform/resolvers:将 Zod 连接到 React Hook Form 的适配器
2. 创建你的第一个表单
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
// 1. Define validation schema
const loginSchema = z.object({
email: z.email({ error: 'Invalid email address' }),
password: z.string().min(8, 'Password must be at least 8 characters'),
})
// 2. Infer TypeScript type from schema
type LoginFormData = z.infer<typeof loginSchema>
function LoginForm() {
// 3. Initialize form with zodResolver
const {
register,
handleSubmit,
formState: { errors, isSubmitting },
} = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
defaultValues: {
email: '',
password: '',
},
})
// 4. Handle form submission
const onSubmit = async (data: LoginFormData) => {
// Data is guaranteed to be valid here
console.log('Valid data:', data)
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="email">Email</label>
<input id="email" type="email" {...register('email')} />
{errors.email && (
<span role="alert" className="error">
{errors.email.message}
</span>
)}
</div>
<div>
<label htmlFor="password">Password</label>
<input id="password" type="password" {...register('password')} />
{errors.password && (
<span role="alert" className="error">
{errors.password.message}
</span>
)}
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Logging in...' : 'Login'}
</button>
</form>
)
}
重要:
- 始终设置
defaultValues,以避免出现「非受控变为受控」警告 - 使用
zodResolver(schema)接入 Zod 验证 - 使用
z.infer<typeof schema>为表单添加类型,确保完整的类型安全 - 在客户端和服务器端都进行验证(绝不要只相信客户端验证)
模板:参见 templates/basic-form.tsx,查看完整的可运行示例
3. 添加服务器端验证
// server/api/login.ts
import { z } from 'zod'
// SAME schema on server
const loginSchema = z.object({
email: z.email({ error: 'Invalid email address' }),
password: z.string().min(8, 'Password must be at least 8 characters'),
})
export async function loginHandler(req: Request) {
try {
const data = loginSchema.parse(await req.json())
// Data is type-safe and validated
return { success: true }
} catch (error) {
if (error instanceof z.ZodError) {
return { success: false, errors: z.flattenError(error).fieldErrors }
}
throw error
}
}
为什么要进行服务器端验证:
- 客户端验证可以被绕过(检查元素、Postman、curl)
- 服务器端验证是你的安全防护层
- 使用相同的 Zod 架构 = 单一事实来源
模板:参见 templates/server-validation.tsx
核心概念
useForm Hook
const {
register, // Register input fields
handleSubmit, // Wrap onSubmit handler
formState, // Form state (errors, isValid, isDirty, etc.)
setValue, // Set field value programmatically
getValues, // Get current form values
watch, // Watch field values
reset, // Reset form to defaults
trigger, // Trigger validation manually
control, // For Controller/useController
} = useForm<FormData>({
resolver: zodResolver(schema),
mode: 'onSubmit', // When to validate
defaultValues: {}, // Initial values (REQUIRED)
})
验证模式:
onSubmit- 提交时验证(性能最佳)onChange- 每次变更时验证(实时反馈)onBlur- 字段失去焦点时验证(良好的平衡)all- 在提交、失焦和变更时验证
参考:参见 references/rhf-api-reference.md,查看完整 API
Zod 架构基础
import { z } from 'zod'
// Basic types
const schema = z.object({
email: z.email({ error: 'Invalid email' }),
age: z.number().min(18, 'Must be 18+'),
terms: z.boolean().refine(val => val === true, 'Must accept terms'),
})
// Nested objects
const addressSchema = z.object({
user: z.object({
name: z.string(),
email: z.email(),
}),
address: z.object({
street: z.string(),
city: z.string(),
zip: z.string().regex(/^\d{5}$/),
}),
})
// Arrays
const tagsSchema = z.object({
tags: z.array(z.string()).min(1, 'At least one tag required'),
})
// Optional and nullable
const optionalSchema = z.object({
middleName: z.string().optional(),
nickname: z.string().nullable(),
bio: z.string().nullish(), // optional AND nullable
})
参考:参见 references/zod-schemas-guide.md,查看完整模式
重要规则
务必做到
✅ 始终设置 defaultValues - 避免出现「非受控变为受控」警告
✅ 使用 zodResolver 进行验证 - 将 Zod 架构连接到 React Hook Form
✅ 从架构推断类型 - 使用 z.infer<typeof schema> 确保类型安全
✅ 也在服务器端进行验证 - 客户端验证可以被绕过
✅ 对原生输入使用 .register() - 简单且高效
✅ 对自定义组件使用 Controller - 适用于组件库(MUI、Chakra 等)
✅ 以无障碍的方式处理错误 - 使用 role="alert" 供屏幕阅读器读取
✅ 提交后重置表单 - 使用 reset() 清除表单状态
表单模式:参见 templates/,了解以下内容:
basic-form.tsx- 简单的登录/注册表单advanced-form.tsx- 嵌套对象、数组、动态字段shadcn-form.tsx- 与 shadcn/ui 集成multi-step-form.tsx- 向导/步骤条表单async-validation.tsx- 异步字段验证
绝不要做
❌ 绝不要跳过 defaultValues - 会导致「非受控变为受控」错误
❌ 绝不要只使用客户端验证 - 这会带来安全漏洞
❌ 绝不要直接修改表单值 - 改用 setValue()
❌ 绝不要忽视无障碍性 - 始终使用适当的标签和 ARIA
❌ 绝不要忘记在 isSubmitting 时禁用提交 - 防止重复提交
性能:参见 references/performance-optimization.md,了解以下内容:
- 何时使用
mode: 'onBlur'而非'onChange' useWatch与watch()的区别- 重新渲染优化策略
无障碍性:参见 references/accessibility.md,了解以下内容:
- 正确关联标签
- 错误播报
- 焦点管理
- 键盘导航
最常见的 5 个严重错误
错误 #1:非受控变为受控警告 ⚠️
错误:
Warning: A component is changing an uncontrolled input to be controlled
原因:未设置 defaultValues
解决方案:
// ❌ BAD
const form = useForm()
// ✅ GOOD
const form = useForm({
defaultValues: {
email: '',
password: '',
}
})
错误 #2:Zod v4 类型推断问题
**错误:**类型推断无法正常工作
解决方案:
// Explicitly type useForm if needed
const form = useForm<z.infer<typeof schema>>({
resolver: zodResolver(schema),
})
错误 #3:未找到 Resolver
错误:
Module not found: Can't resolve '@hookform/resolvers/zod'
解决方案:
# Install the resolvers package
bun add @hookform/[email protected]
错误 #4:数组字段问题
**错误:**动态数组字段无法与 useFieldArray 配合使用
解决方案:
const { fields, append, remove } = useFieldArray({
control,
name: "items" // Must match schema field name exactly
})
模板:参见 templates/dynamic-fields.tsx
错误 #5:自定义组件验证失败
错误: 第三方组件(MUI、Chakra)无法验证
解决方案:
使用 Controller,而不是 register:
<Controller
name="date"
control={control}
render={({ field }) => (
<DatePicker {...field} />
)}
/>
参考: 查看 references/error-handling.md,了解所有模式
全部 12 个错误: 查看 references/top-errors.md,获取完整文档
常见模式
基本表单
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
const schema = z.object({
name: z.string().min(1, 'Name required'),
email: z.email({ error: 'Invalid email' }),
})
type FormData = z.infer<typeof schema>
function MyForm() {
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: { name: '', email: '' }
})
const onSubmit = (data: FormData) => console.log(data)
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} />
{errors.name && <span>{errors.name.message}</span>}
<button type="submit">Submit</button>
</form>
)
}
模板: 查看 templates/basic-form.tsx
动态字段(useFieldArray)
import { useForm, useFieldArray } from 'react-hook-form'
const schema = z.object({
items: z.array(
z.object({
name: z.string(),
quantity: z.number().min(1)
})
).min(1, 'At least one item required')
})
function DynamicForm() {
const { control, handleSubmit } = useForm({
resolver: zodResolver(schema),
defaultValues: { items: [{ name: '', quantity: 1 }] }
})
const { fields, append, remove } = useFieldArray({
control,
name: 'items'
})
return (
<form>
{fields.map((field, index) => (
<div key={field.id}>
<input {...register(`items.${index}.name`)} />
<button onClick={() => remove(index)}>Remove</button>
</div>
))}
<button onClick={() => append({ name: '', quantity: 1 })}>
Add Item
</button>
</form>
)
}
模板: 查看 templates/dynamic-fields.tsx
异步验证
const schema = z.object({
username: z.string()
.min(3)
.refine(async (username) => {
const response = await fetch(`/api/check-username?username=${username}`)
const { available } = await response.json()
return available
}, 'Username already taken')
})
模板: 查看 templates/async-validation.tsx
多步骤表单
function MultiStepForm() {
const [step, setStep] = useState(1)
const form = useForm({
resolver: zodResolver(schema),
mode: 'onBlur' // Validate each step before proceeding
})
const onSubmit = async (data) => {
if (step < 3) {
setStep(step + 1)
} else {
// Final submission
await submitForm(data)
}
}
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
{step === 1 && <Step1Fields />}
{step === 2 && <Step2Fields />}
{step === 3 && <Step3Fields />}
<button type="submit">
{step < 3 ? 'Next' : 'Submit'}
</button>
</form>
)
}
模板: 查看 templates/multi-step-form.tsx
shadcn/ui 集成
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'
function ShadcnForm() {
const form = useForm({
resolver: zodResolver(schema),
defaultValues: { email: '' }
})
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
)
}
参考: 查看 references/shadcn-integration.md,了解完整模式
模板: 查看 templates/shadcn-form.tsx
使用内置资源
模板(templates/)
可直接复制粘贴的示例:
- basic-form.tsx - 带验证的简单登录/注册表单
- advanced-form.tsx - 嵌套对象、数组和条件字段
- shadcn-form.tsx - shadcn/ui Form 组件集成
- multi-step-form.tsx - 带步骤验证的向导/步骤条表单
- dynamic-fields.tsx - 使用 useFieldArray 实现动态表单字段
- async-validation.tsx - 异步字段验证(用户名检查等)
- server-validation.tsx - 使用 Zod 进行服务器端验证
- custom-error-display.tsx - 自定义错误消息组件
- package.json - 软件包版本和脚本
参考文档(references/)
详细文档:
- top-errors.md - 全部 12 种常见错误及其解决方案和来源
- rhf-api-reference.md - 完整的 React Hook Form API 参考
- zod-schemas-guide.md - 全面的 Zod 模式
- shadcn-integration.md - shadcn/ui Form 集成指南
- error-handling.md - 错误显示模式和无障碍性
- performance-optimization.md - 重新渲染优化策略
- accessibility.md - WCAG 合规性和屏幕阅读器支持
- links-to-official-docs.md - 整理后的官方文档链接
何时加载参考文档
| 参考文档 | 加载时机…… |
|---|---|
top-errors.md | 调试验证问题、类型错误或“非受控转受控”警告时 |
rhf-api-reference.md | 需要了解 useForm、register、Controller、formState 的完整 API 时 |
zod-schemas-guide.md | 构建复杂模式(嵌套、数组、条件验证、异步验证)时 |
shadcn-integration.md | 使用 shadcn/ui Form、FormField、FormItem 组件时 |
error-handling.md | 自定义错误显示、验证时机、错误消息模式时 |
performance-optimization.md | 表单重新渲染过多,或优化 watch/useWatch 时 |
accessibility.md | 需要 WCAG 合规性、屏幕阅读器或键盘导航时 |
links-to-official-docs.md | 需要官方文档链接时 |
性能提示
快速提示:
- 使用
mode: 'onBlur',平衡用户体验和性能 - 对特定字段使用
useWatch,而不是watch() - 在组件外缓存验证模式
- 对条件字段使用
shouldUnregister: false - 避免不带参数调用
watch()(这会监听所有字段)
参考: 查看 references/performance-optimization.md,了解完整策略
无障碍性
快速检查清单:
- ✅ 对所有输入框使用
<label htmlFor="fieldId"> - ✅ 为错误消息添加
role="alert" - ✅ 在无效字段上使用
aria-invalid="true" - ✅ 确保键盘导航正常工作(Tab、Enter、Escape)
- ✅ 提供清晰且可执行的错误消息
参考: 查看 references/accessibility.md,了解 WCAG 合规性指南
验证模式(Zod)
常见模式:
// Email
z.email({ error: 'Invalid email' })
// Password (min 8 chars, 1 uppercase, 1 number)
z.string()
.min(8)
.regex(/[A-Z]/, 'Need uppercase')
.regex(/[0-9]/, 'Need number')
// URL
z.url({ error: 'Invalid URL' })
// Date
z.datetime() // ISO 8601
z.date() // JS Date object
// File upload
z.instanceof(File)
.refine(file => file.size <= 5000000, 'Max 5MB')
.refine(
file => ['image/jpeg', 'image/png'].includes(file.type),
'Only JPEG/PNG allowed'
)
// Custom validation
z.string().refine(
val => val !== 'admin',
'Username "admin" is reserved'
)
// Async validation
z.string().refine(
async (username) => {
const available = await checkUsername(username)
return available
},
'Username already taken'
)
参考: 查看 references/zod-schemas-guide.md,了解所有模式
依赖项
必需:
[email protected]- 表单状态管理[email protected]- Schema 验证@hookform/[email protected]- 验证适配器
可选:
@radix-ui/react-label@latest- 用于 shadcn/ui 集成class-variance-authority@latest- 用于 shadcn/ui 样式
官方文档
- React Hook Form:https://react-hook-form.com/
- Zod:https://zod.dev/
- @hookform/resolvers:https://github.com/react-hook-form/resolvers
- shadcn/ui Form:https://ui.shadcn.com/docs/components/form
- GitHub:https://github.com/react-hook-form/react-hook-form
参考:查看 references/links-to-official-docs.md 中整理的链接
故障排除
“非受控转受控”警告
解决方案:始终设置 defaultValues → 查看 references/top-errors.md #2
Zod v4 的类型推断问题
解决方案:显式指定类型 useForm<z.infer<typeof schema>> → 查看 references/top-errors.md #1
找不到 Resolver 错误
解决方案:安装 @hookform/resolvers 包 → 查看 references/top-errors.md #3
自定义组件无法验证
解决方案:使用 Controller 替代 register → 查看 references/top-errors.md #5
表单重新渲染过多
解决方案:使用 mode: 'onBlur' 和 useWatch → 查看 references/performance-optimization.md
生产环境示例
此技能基于生产环境中的实践模式:
- 真实场景中的表单:登录、注册、结账、多步骤向导
- 验证:客户端和服务端使用共享的 Zod Schema
- 无障碍性:符合 WCAG 2.1 AA 标准
- 性能:优化以尽量减少重新渲染
Token 节省:约 60%(包含模板的全面表单模式) 错误预防:100%(针对所有 12 个已记录问题提供解决方案) 已准备好用于生产环境! ✅