react-hook-form-zod

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

React Hook Form + Zod Validation

React Hook Form + Zod 表单验证

Status: Production Ready ✅ Last Verified: 2026-01-20 Latest Versions: react-hook-form@7.71.1, zod@4.3.5, @hookform/resolvers@5.2.2

状态:可用于生产环境 ✅ 最后验证时间:2026-01-20 最新版本:react-hook-form@7.71.1, zod@4.3.5, @hookform/resolvers@5.2.2

Quick Start

快速开始

bash
npm install react-hook-form@7.70.0 zod@4.3.5 @hookform/resolvers@5.2.2
Basic Form Pattern:
typescript
const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
})

type FormData = z.infer<typeof schema>

const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
  resolver: zodResolver(schema),
  defaultValues: { email: '', password: '' }, // REQUIRED to prevent uncontrolled warnings
})

<form onSubmit={handleSubmit(onSubmit)}>
  <input {...register('email')} />
  {errors.email && <span role="alert">{errors.email.message}</span>}
</form>
Server Validation (CRITICAL - never skip):
typescript
// SAME schema on server
const data = schema.parse(await req.json())

bash
npm install react-hook-form@7.70.0 zod@4.3.5 @hookform/resolvers@5.2.2
基础表单模式:
typescript
const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
})

type FormData = z.infer<typeof schema>

const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
  resolver: zodResolver(schema),
  defaultValues: { email: '', password: '' }, // 必须设置,防止非受控组件警告
})

<form onSubmit={handleSubmit(onSubmit)}>
  <input {...register('email')} />
  {errors.email && <span role="alert">{errors.email.message}</span>}
</form>
服务端验证(至关重要 - 绝不能跳过):
typescript
// 服务端使用同一套schema
const data = schema.parse(await req.json())

Key Patterns

核心模式

useForm Options (validation modes):
  • mode: 'onSubmit'
    (default) - Best performance
  • mode: 'onBlur'
    - Good balance
  • mode: 'onChange'
    - Live feedback, more re-renders
  • shouldUnregister: true
    - Remove field data when unmounted (use for multi-step forms)
Zod Refinements (cross-field validation):
typescript
z.object({ password: z.string(), confirm: z.string() })
  .refine((data) => data.password === data.confirm, {
    message: "Passwords don't match",
    path: ['confirm'], // CRITICAL: Error appears on this field
  })
Zod Transforms:
typescript
z.string().transform((val) => val.toLowerCase()) // Data manipulation
z.string().transform(parseInt).refine((v) => v > 0) // Chain with refine
Zod v4.3.0+ Features:
typescript
// Exact optional (can omit field, but NOT undefined)
z.string().exactOptional()

// Exclusive union (exactly one must match)
z.xor([z.string(), z.number()])

// Import from JSON Schema
z.fromJSONSchema({ type: "object", properties: { name: { type: "string" } } })
zodResolver connects Zod to React Hook Form, preserving type safety

useForm 配置项(验证模式):
  • mode: 'onSubmit'
    (默认)- 性能最佳
  • mode: 'onBlur'
    - 平衡体验与性能
  • mode: 'onChange'
    - 实时反馈,重渲染更多
  • shouldUnregister: true
    - 组件卸载时移除字段数据(适用于多步骤表单)
Zod 自定义验证(跨字段验证):
typescript
z.object({ password: z.string(), confirm: z.string() })
  .refine((data) => data.password === data.confirm, {
    message: "两次输入的密码不一致",
    path: ['confirm'], // 关键:错误提示显示在该字段上
  })
Zod 数据转换:
typescript
z.string().transform((val) => val.toLowerCase()) // 数据处理
z.string().transform(parseInt).refine((v) => v > 0) // 链式调用验证
Zod v4.3.0+ 新特性:
typescript
// 严格可选(可省略字段,但不能为undefined)
z.string().exactOptional()

// 互斥联合(必须且只能匹配其中一个)
z.xor([z.string(), z.number()])

// 从JSON Schema导入
z.fromJSONSchema({ type: "object", properties: { name: { type: "string" } } })
zodResolver 用于连接Zod与React Hook Form,保留类型安全性

Registration

字段注册

register (for standard HTML inputs):
typescript
<input {...register('email')} /> // Uncontrolled, best performance
Controller (for third-party components):
typescript
<Controller
  name="category"
  control={control}
  render={({ field }) => <CustomSelect {...field} />} // MUST spread {...field}
/>
When to use Controller: React Select, date pickers, custom components without ref. Otherwise use
register
.

register(适用于标准HTML输入框):
typescript
<input {...register('email')} /> // 非受控组件,性能最佳
Controller(适用于第三方组件):
typescript
<Controller
  name="category"
  control={control}
  render={({ field }) => <CustomSelect {...field} />} // 必须展开{...field}
/>
何时使用Controller: React Select、日期选择器、无ref的自定义组件。其他情况优先使用
register

Error Handling

错误处理

Display errors:
typescript
{errors.email && <span role="alert">{errors.email.message}</span>}
{errors.address?.street?.message} // Nested errors (use optional chaining)
Server errors:
typescript
const onSubmit = async (data) => {
  const res = await fetch('/api/submit', { method: 'POST', body: JSON.stringify(data) })
  if (!res.ok) {
    const { errors: serverErrors } = await res.json()
    Object.entries(serverErrors).forEach(([field, msg]) => setError(field, { message: msg }))
  }
}

显示错误:
typescript
{errors.email && <span role="alert">{errors.email.message}</span>}
{errors.address?.street?.message} // 嵌套字段错误(使用可选链操作符)
服务端错误处理:
typescript
const onSubmit = async (data) => {
  const res = await fetch('/api/submit', { method: 'POST', body: JSON.stringify(data) })
  if (!res.ok) {
    const { errors: serverErrors } = await res.json()
    Object.entries(serverErrors).forEach(([field, msg]) => setError(field, { message: msg }))
  }
}

Advanced Patterns

进阶模式

useFieldArray (dynamic lists):
typescript
const { fields, append, remove } = useFieldArray({ control, name: 'contacts' })

{fields.map((field, index) => (
  <div key={field.id}> {/* CRITICAL: Use field.id, NOT index */}
    <input {...register(`contacts.${index}.name` as const)} />
    {errors.contacts?.[index]?.name && <span>{errors.contacts[index].name.message}</span>}
    <button onClick={() => remove(index)}>Remove</button>
  </div>
))}
<button onClick={() => append({ name: '', email: '' })}>Add</button>
Async Validation (debounce):
typescript
const debouncedValidation = useDebouncedCallback(() => trigger('username'), 500)
Multi-Step Forms:
typescript
const step1 = z.object({ name: z.string(), email: z.string().email() })
const step2 = z.object({ address: z.string() })
const fullSchema = step1.merge(step2)

const nextStep = async () => {
  const isValid = await trigger(['name', 'email']) // Validate specific fields
  if (isValid) setStep(2)
}
Conditional Validation:
typescript
z.discriminatedUnion('accountType', [
  z.object({ accountType: z.literal('personal'), name: z.string() }),
  z.object({ accountType: z.literal('business'), companyName: z.string() }),
])
Conditional Fields with shouldUnregister:
typescript
const form = useForm({
  resolver: zodResolver(schema),
  shouldUnregister: false, // Keep values when fields unmount (default)
})

// Or use conditional schema validation:
z.object({
  showAddress: z.boolean(),
  address: z.string(),
}).refine((data) => {
  if (data.showAddress) {
    return data.address.length > 0;
  }
  return true;
}, {
  message: "Address is required",
  path: ["address"],
})

useFieldArray(动态列表):
typescript
const { fields, append, remove } = useFieldArray({ control, name: 'contacts' })

{fields.map((field, index) => (
  <div key={field.id}> {/* 关键:使用field.id作为key,而非index */}
    <input {...register(`contacts.${index}.name` as const)} />
    {errors.contacts?.[index]?.name && <span>{errors.contacts[index].name.message}</span>}
    <button onClick={() => remove(index)}>移除</button>
  </div>
))}
<button onClick={() => append({ name: '', email: '' })}>添加</button>
异步验证(防抖):
typescript
const debouncedValidation = useDebouncedCallback(() => trigger('username'), 500)
多步骤表单:
typescript
const step1 = z.object({ name: z.string(), email: z.string().email() })
const step2 = z.object({ address: z.string() })
const fullSchema = step1.merge(step2)

const nextStep = async () => {
  const isValid = await trigger(['name', 'email']) // 验证指定字段
  if (isValid) setStep(2)
}
条件验证:
typescript
z.discriminatedUnion('accountType', [
  z.object({ accountType: z.literal('personal'), name: z.string() }),
  z.object({ accountType: z.literal('business'), companyName: z.string() }),
])
带shouldUnregister的条件字段:
typescript
const form = useForm({
  resolver: zodResolver(schema),
  shouldUnregister: false, // 组件卸载时保留字段值(默认)
})

// 或使用条件Schema验证:
z.object({
  showAddress: z.boolean(),
  address: z.string(),
}).refine((data) => {
  if (data.showAddress) {
    return data.address.length > 0;
  }
  return true;
}, {
  message: "地址为必填项",
  path: ["address"],
})

shadcn/ui Integration

shadcn/ui 集成

Note: shadcn/ui deprecated the Form component. Use the Field component for new implementations (check latest docs).
Common Import Mistake: IDEs/AI may auto-import
Form
from "react-hook-form" instead of from shadcn. Always import:
typescript
// ✅ Correct:
import { useForm } from "react-hook-form";
import { Form, FormField, FormItem } from "@/components/ui/form"; // shadcn

// ❌ Wrong (auto-import mistake):
import { useForm, Form } from "react-hook-form";
Legacy Form component:
typescript
<FormField control={form.control} name="username" render={({ field }) => (
  <FormItem>
    <FormControl><Input {...field} /></FormControl>
    <FormMessage />
  </FormItem>
)} />

注意: shadcn/ui 已弃用Form组件。新实现请使用Field组件(请查阅最新文档)。
常见导入错误: IDE/AI可能会自动从"react-hook-form"导入
Form
,而非shadcn的。请始终按以下方式导入:
typescript
// ✅ 正确:
import { useForm } from "react-hook-form";
import { Form, FormField, FormItem } from "@/components/ui/form"; // shadcn

// ❌ 错误(自动导入问题):
import { useForm, Form } from "react-hook-form";
旧版Form组件:
typescript
<FormField control={form.control} name="username" render={({ field }) => (
  <FormItem>
    <FormControl><Input {...field} /></FormControl>
    <FormMessage />
  </FormItem>
)} />

Performance

性能优化

  • Use
    register
    (uncontrolled) over
    Controller
    (controlled) for standard inputs
  • Use
    watch('email')
    not
    watch()
    (isolates re-renders to specific fields)
  • shouldUnregister: true
    for multi-step forms (clears data on unmount)
  • 对于标准输入框,优先使用
    register
    (非受控)而非
    Controller
    (受控)
  • 使用
    watch('email')
    而非
    watch()
    (仅监听指定字段,减少重渲染)
  • 多步骤表单使用
    shouldUnregister: true
    (卸载时清除数据)

Large Forms (300+ Fields)

大型表单(300+字段)

Warning: Forms with 300+ fields using a resolver (Zod/Yup) AND reading
formState
properties can freeze for 10-15 seconds during registration. (Issue #13129)
Performance Characteristics:
  • Clean (no resolver, no formState read): Almost immediate
  • With resolver only: Almost immediate
  • With formState read only: Almost immediate
  • With BOTH resolver + formState read: ~9.5 seconds for 300 fields
Workarounds:
  1. Avoid destructuring formState - Read properties inline only when needed:
typescript
// ❌ Slow with 300+ fields:
const { isDirty, isValid } = form.formState;

// ✅ Fast:
const handleSubmit = () => {
  if (!form.formState.isValid) return; // Read inline only when needed
};
  1. Use mode: "onSubmit" - Don't validate on every change:
typescript
const form = useForm({
  resolver: zodResolver(largeSchema),
  mode: "onSubmit", // Validate only on submit, not onChange
});
  1. Split into sub-forms - Multiple smaller forms with separate schemas:
typescript
// Instead of one 300-field form, use 5-6 forms with 50-60 fields each
const form1 = useForm({ resolver: zodResolver(schema1) }); // Fields 1-50
const form2 = useForm({ resolver: zodResolver(schema2) }); // Fields 51-100
  1. Lazy render fields - Use tabs/accordion to mount only visible fields:
typescript
// Only mount fields for active tab, reduces initial registration time
{activeTab === 'personal' && <PersonalInfoFields />}
{activeTab === 'address' && <AddressFields />}

警告: 使用解析器(Zod/Yup)且读取
formState
属性的300+字段表单,在注册阶段可能会冻结10-15秒。(Issue #13129)
性能特征:
  • 纯净状态(无解析器、无formState读取): 几乎瞬间完成
  • 仅使用解析器: 几乎瞬间完成
  • 仅读取formState: 几乎瞬间完成
  • 同时使用解析器+读取formState: 300个字段约需9.5秒
解决方案:
  1. 避免解构formState - 仅在需要时内联读取属性:
typescript
// ❌ 300+字段时缓慢:
const { isDirty, isValid } = form.formState;

// ✅ 快速:
const handleSubmit = () => {
  if (!form.formState.isValid) return; // 仅在需要时内联读取
};
  1. 使用mode: "onSubmit" - 不要在每次变更时验证:
typescript
const form = useForm({
  resolver: zodResolver(largeSchema),
  mode: "onSubmit", // 仅在提交时验证,而非变更时
});
  1. 拆分为子表单 - 使用多个独立Schema的小型表单:
typescript
// 不要使用一个300字段的表单,而是拆分为5-6个50-60字段的表单
const form1 = useForm({ resolver: zodResolver(schema1) }); // 字段1-50
const form2 = useForm({ resolver: zodResolver(schema2) }); // 字段51-100
  1. 懒渲染字段 - 使用标签页/折叠面板仅渲染可见字段:
typescript
// 仅渲染当前激活标签页的字段,减少初始注册时间
{activeTab === 'personal' && <PersonalInfoFields />}
{activeTab === 'address' && <AddressFields />}

Critical Rules

关键规则

Always set defaultValues (prevents uncontrolled→controlled warnings)
Validate on BOTH client and server (client can be bypassed - security!)
Use
field.id
as key
in useFieldArray (not index)
Spread
{...field}
in Controller render
Use
z.infer<typeof schema>
for type inference
Never skip server validation (security vulnerability)
Never mutate values directly (use
setValue()
)
Never mix controlled + uncontrolled patterns
Never use index as key in useFieldArray

始终设置defaultValues(防止非受控→受控组件警告)
同时在客户端和服务端验证(客户端验证可被绕过 - 安全问题!)
在useFieldArray中使用
field.id
作为key
(而非index)
在Controller的render函数中展开
{...field}
✅ **使用
z.infer<typeof schema>
**实现类型推断
绝不能跳过服务端验证(安全漏洞)
绝不能直接修改值(使用
setValue()
绝不能混合受控+非受控模式
绝不能在useFieldArray中使用index作为key

Known Issues (20 Prevented)

已知问题(已规避20项)

  1. Zod v4 Type Inference - #13109: Use
    z.infer<typeof schema>
    explicitly. Resolved in v7.66.x+. Note: @hookform/resolvers has TypeScript compatibility issues with Zod v4 (#813). Workaround: Use
    import { z } from 'zod/v3'
    or wait for resolver update.
  2. Uncontrolled→Controlled Warning - Always set
    defaultValues
    for all fields
  3. Nested Object Errors - Use optional chaining:
    errors.address?.street?.message
  4. Array Field Re-renders - Use
    key={field.id}
    in useFieldArray (not index)
  5. Async Validation Race Conditions - Debounce validation, cancel pending requests
  6. Server Error Mapping - Use
    setError()
    to map server errors to fields
  7. Default Values Not Applied - Set
    defaultValues
    in useForm options (not useState)
  8. Controller Field Not Updating - Always spread
    {...field}
    in render function
  9. useFieldArray Key Warnings - Use
    field.id
    as key (not index)
  10. Schema Refinement Error Paths - Specify
    path
    in refinement:
    refine(..., { path: ['fieldName'] })
  11. Transform vs Preprocess - Use
    transform
    for output,
    preprocess
    for input
  12. Multiple Resolver Conflicts - Use single resolver (zodResolver), combine schemas if needed
  13. Zod v4 Optional Fields Bug - #13102: Setting optional fields (
    .optional()
    ) to empty string
    ""
    incorrectly triggers validation errors. Workarounds: Use
    .nullish()
    ,
    .or(z.literal(""))
    , or
    z.preprocess((val) => val === "" ? undefined : val, z.email().optional())
  14. useFieldArray Primitive Arrays Not Supported - #12570: Design limitation.
    useFieldArray
    only works with arrays of objects, not primitives like
    string[]
    . Workaround: Wrap primitives in objects:
    [{ value: "string" }]
    instead of
    ["string"]
  15. useFieldArray SSR ID Mismatch - #12782: Hydration mismatch warnings with SSR (Remix, Next.js). Field IDs generated on server don't match client. Workaround: Use client-only rendering for field arrays or wait for V8 (uses deterministic
    key
    )
  16. Next.js 16 reset() Validation Bug - #13110: Calling
    form.reset()
    after Server Actions submission causes validation errors on next submit. Fixed in v7.65.0+. Before fix: Use
    setValue()
    instead of
    reset()
  17. Validation Race Condition - #13156: During resolver validation, intermediate render where
    isValidating=false
    but
    errors
    not populated yet. Don't derive validity from errors alone. Use:
    !errors.field && !isValidating
  18. ZodError Thrown in Beta Versions - #12816: Zod v4 beta versions throw
    ZodError
    directly instead of capturing in
    formState.errors
    . Fixed in stable Zod v4.1.x+. Avoid beta versions
  19. Large Form Performance - #13129: 300+ fields with resolver + formState read freezes for 10-15 seconds. See Performance section for 4 workarounds
  20. shadcn Form Import Confusion - IDEs/AI may auto-import
    Form
    from "react-hook-form" instead of shadcn. Always import
    Form
    components from
    @/components/ui/form

  1. Zod v4类型推断 - #13109: 显式使用
    z.infer<typeof schema>
    。该问题在v7.66.x+中已解决。注意: @hookform/resolvers与Zod v4存在TypeScript兼容性问题(#813)。解决方案: 使用
    import { z } from 'zod/v3'
    或等待解析器更新。
  2. 非受控→受控警告 - 始终为所有字段设置
    defaultValues
  3. 嵌套对象错误 - 使用可选链操作符:
    errors.address?.street?.message
  4. 数组字段重渲染 - 在useFieldArray中使用
    key={field.id}
    (而非index)
  5. 异步验证竞态条件 - 对验证进行防抖,取消待处理请求
  6. 服务端错误映射 - 使用
    setError()
    将服务端错误映射到对应字段
  7. 默认值未生效 - 在useForm配置项中设置
    defaultValues
    (而非useState)
  8. Controller字段未更新 - 始终在render函数中展开
    {...field}
  9. useFieldArray Key警告 - 使用
    field.id
    作为key(而非index)
  10. Schema自定义验证错误路径 - 在refine中指定
    path
    :
    refine(..., { path: ['fieldName'] })
  11. Transform vs Preprocess - 使用
    transform
    处理输出,
    preprocess
    处理输入
  12. 多解析器冲突 - 使用单个解析器(zodResolver),如有需要合并Schema
  13. Zod v4可选字段Bug - #13102: 将可选字段(
    .optional()
    )设置为空字符串
    ""
    会错误触发验证错误。解决方案: 使用
    .nullish()
    .or(z.literal(""))
    z.preprocess((val) => val === "" ? undefined : val, z.email().optional())
  14. useFieldArray不支持原始数组 - #12570: 设计限制。
    useFieldArray
    仅支持对象数组,不支持
    string[]
    等原始类型数组。解决方案: 将原始类型包装为对象:
    [{ value: "string" }]
    替代
    ["string"]
  15. useFieldArray SSR ID不匹配 - #12782: SSR(Remix、Next.js)下的 hydration 不匹配警告。服务端生成的字段ID与客户端不一致。解决方案: 对字段数组使用客户端仅渲染,或等待V8版本(使用确定性
    key
  16. Next.js 16 reset()验证Bug - #13110: Server Actions提交后调用
    form.reset()
    会导致下一次提交时触发验证错误。该问题在v7.65.0+中已修复。修复前: 使用
    setValue()
    替代
    reset()
  17. 验证竞态条件 - #13156: 解析器验证期间,会出现
    isValidating=false
    errors
    尚未填充的中间渲染状态。不要仅通过errors判断有效性。应使用:
    !errors.field && !isValidating
  18. Beta版本中抛出ZodError - #12816: Zod v4 Beta版本会直接抛出
    ZodError
    ,而非捕获到
    formState.errors
    中。该问题在稳定版Zod v4.1.x+中已修复。请避免使用Beta版本
  19. 大型表单性能问题 - #13129: 300+字段且同时使用解析器+读取formState的表单会冻结10-15秒。请查看性能优化部分的4种解决方案
  20. shadcn Form导入混淆 - IDE/AI可能会自动从"react-hook-form"导入
    Form
    ,而非shadcn的。请始终从
    @/components/ui/form
    导入Form组件

Upcoming Changes in V8 (Beta)

V8版本(Beta)的即将到来的变更

React Hook Form v8 (currently in beta as of v8.0.0-beta.1, released 2026-01-11) introduces breaking changes. RFC Discussion #7433
Breaking Changes:
  1. useFieldArray:
    id
    key
    :
typescript
// V7:
const { fields } = useFieldArray({ control, name: "items" });
fields.map(field => <div key={field.id}>...</div>)

// V8:
const { fields } = useFieldArray({ control, name: "items" });
fields.map(field => <div key={field.key}>...</div>)
// keyName prop removed
  1. Watch component:
    names
    name
    :
typescript
// V7:
<Watch names={["email", "password"]} />

// V8:
<Watch name={["email", "password"]} />
  1. watch() callback API removed:
typescript
// V7:
watch((data, { name, type }) => {
  console.log(data, name, type);
});

// V8: Use useWatch or manual subscription
const data = useWatch({ control });
useEffect(() => {
  console.log(data);
}, [data]);
  1. setValue() no longer updates useFieldArray:
typescript
// V7:
setValue("items", newArray); // Updates field array

// V8: Must use replace() API
const { replace } = useFieldArray({ control, name: "items" });
replace(newArray);
V8 Benefits:
  • Fixes SSR hydration mismatch (deterministic
    key
    instead of random
    id
    )
  • Improved performance
  • Better TypeScript inference
Migration Timeline: V8 is in beta. Stable release date TBD. Monitor releases for stable version.

React Hook Form v8(截至2026-01-11为Beta版,版本v8.0.0-beta.1)引入了破坏性变更。RFC讨论 #7433
破坏性变更:
  1. useFieldArray:
    id
    key
    :
typescript
// V7:
const { fields } = useFieldArray({ control, name: "items" });
fields.map(field => <div key={field.id}>...</div>)

// V8:
const { fields } = useFieldArray({ control, name: "items" });
fields.map(field => <div key={field.key}>...</div>)
// 移除了keyName属性
  1. Watch组件:
    names
    name
    :
typescript
// V7:
<Watch names={["email", "password"]} />

// V8:
<Watch name={["email", "password"]} />
  1. watch()回调API被移除:
typescript
// V7:
watch((data, { name, type }) => {
  console.log(data, name, type);
});

// V8: 使用useWatch或手动订阅
const data = useWatch({ control });
useEffect(() => {
  console.log(data);
}, [data]);
  1. setValue()不再更新useFieldArray:
typescript
// V7:
setValue("items", newArray); // 更新字段数组

// V8: 必须使用replace() API
const { replace } = useFieldArray({ control, name: "items" });
replace(newArray);
V8版本优势:
  • 修复了SSR hydration不匹配问题(使用确定性
    key
    替代随机
    id
  • 性能提升
  • 更优的TypeScript类型推断
迁移时间线: V8目前为Beta版。稳定版发布日期待定。请关注发布页面获取稳定版信息。

Bundled Resources

配套资源

Templates: basic-form.tsx, advanced-form.tsx, shadcn-form.tsx, server-validation.ts, async-validation.tsx, dynamic-fields.tsx, multi-step-form.tsx, package.json
References: zod-schemas-guide.md, rhf-api-reference.md, error-handling.md, performance-optimization.md, shadcn-integration.md, top-errors.md

License: MIT | Last Verified: 2026-01-20 | Skill Version: 2.1.0 | Changes: Added 8 new known issues (Zod v4 optional fields bug, useFieldArray primitives limitation, SSR hydration mismatch, performance guidance for large forms, Next.js 16 reset() bug, validation race condition, ZodError thrown in beta, shadcn import confusion), added Zod v4.3.0 features (.exactOptional(), .xor(), z.fromJSONSchema()), added conditional field patterns with shouldUnregister, added V8 beta breaking changes section, expanded Zod v4 resolver compatibility notes, updated to react-hook-form@7.71.1
模板: basic-form.tsx, advanced-form.tsx, shadcn-form.tsx, server-validation.ts, async-validation.tsx, dynamic-fields.tsx, multi-step-form.tsx, package.json
参考文档: zod-schemas-guide.md, rhf-api-reference.md, error-handling.md, performance-optimization.md, shadcn-integration.md, top-errors.md

许可证: MIT | 最后验证时间: 2026-01-20 | 技能版本: 2.1.0 | 变更记录: 新增8项已知问题(Zod v4可选字段Bug、useFieldArray原始类型限制、SSR hydration不匹配、大型表单性能指南、Next.js 16 reset() Bug、验证竞态条件、Beta版本抛出ZodError、shadcn导入混淆),新增Zod v4.3.0特性(.exactOptional(), .xor(), z.fromJSONSchema()),新增带shouldUnregister的条件字段模式,新增V8 Beta版破坏性变更部分,扩展Zod v4解析器兼容性说明,更新至react-hook-form@7.71.1",