React Hook Form + Zod:优雅构建 React 表单

最近在浏览 shadcn/ui 的 表单文档 (Forms)

时,发现它并没有去造一个全新的表单轮子,而是推荐使用两个神仙库:React Hook Form 和 Zod。

为什么 shadcn/ui 会选择这个组合?我使用下来总结有以下几点:

  1. react-hook-form有极致的性能与状态管理

  2. Zod对表单校验规则的全面封装

在表单场景中他俩就是天作之合、强强联手

1. React Hook Form:高性能的表单状态管理

React Hook Form(RHF)是一个 轻量、高性能的 React 表单库

它的核心理念是:

让表单尽可能接近原生 HTML form。

相比传统的表单写法:

js 复制代码
const [email, setEmail] = useState("")
const [password, setPassword] = useState("")

受控组件(Controlled Components)每次输入都会触发整个组件的重渲染。

React Hook Form (RHF) 采用了非受控组件的思想,用 ref 直接读取表单项的值。


React Hook Form 的核心 API

React Hook Form 的 API 非常少,最核心的是以下几个。

1 useForm

useForm 用于创建表单实例。

ts 复制代码
const {
  register,
  handleSubmit,
  control,
  reset,
  watch,
  formState
} = useForm()

其中:

  • register:注册字段
  • handleSubmit:处理提交
  • control:控制复杂组件
  • formState:表单状态

2 register

用于注册input字段,它会返回 onChange、onBlur、name 和 ref。你可以直接用解构语法 {...register('字段名')} 绑定到原生 上

jsx 复制代码
<input {...register("email")} />

这行代码实际上会:

  • 注册字段到表单
  • 绑定 value、onChange、ref

提交时 React Hook Form 会自动收集数据:

json 复制代码
{
  "email": "example@email.com"
}

3 handleSubmit

处理表单提交。

jsx 复制代码
<form onSubmit={handleSubmit(onSubmit)}>
ts 复制代码
const onSubmit = (data) => {
  console.log(data)
}

4 formState

formState 用于获取表单状态,有errors,isSubmitting,isValid,dirtyFields,touchedFields等状态

例如显示表单校验错误信息:

jsx 复制代码
<p>{form.formState.errors.email?.message}</p>

二、Zod:类型安全的 schema validation

表单校验需要一套规则,而 Zod 就是用来定义这些规则的。

你只需要定义一次 Zod Schema,它就能同时为你提供运行时校验和编译时的类型提示,告别重复写 TS 接口和校验逻辑。

例如:

ts 复制代码
import { z } from "zod"

const UserSchema = z.object({
  name: z.string(),
  age: z.number(),
})
UserSchema.parse(data)

如果数据不符合 schema,就会抛出错误。


Zod 常用 API

  1. 字符串校验
ts 复制代码
z.string()
z.string().min(3)
z.string().max(20)
z.string().email()
z.string().url()

例如:

ts 复制代码
z.string().min(3, "Too short")

  1. number
ts 复制代码
z.number().min(0)

  1. optional
ts 复制代码
z.string().optional()

  1. enum
ts 复制代码
z.enum(["todo", "doing", "done"])

  1. object
ts 复制代码
z.object({
  title: z.string(),
  description: z.string()
})

类型推导

z.infer<typeof schema>是 Zod 最"魔法"的 API! 它可以直接根据你写的 Schema 反向推导出 TypeScript 的 type 或 interface.比如在我的项目中:

ts 复制代码
const formSchema = z
    .object({
        title: z.string().trim().min(1, "Title is required."),
        description: z.string().trim().optional(),
        start: z.date().optional(),
        end: z.date().optional(),
    })
    .refine(
        (v) => {
            if (!v.start || !v.end) return true;
            return v.end.getTime() >= v.start.getTime();
        },
        {
            path: ["end"],
            message: "End must be after start.",
        }
    );

type FormData = z.infer<typeof formSchema>

三、一个完整表单示例

下面是一个包含"用户名"和"邮箱"的基础表单组件示例:

ts 复制代码
import React from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';

// 1. 使用 Zod 定义表单的数据模式 (Schema)
const formSchema = z.object({
  username: z.string().min(2, { message: "用户名至少需要 2 个字符" }),
  email: z.string().email({ message: "请输入有效的邮箱地址" }),
});

// 2. 利用 Zod 自动推导 TypeScript 类型
type FormData = z.infer<typeof formSchema>;

export default function MinimalForm() {
  // 3. 初始化 React Hook Form
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<FormData>({
    resolver: zodResolver(formSchema), // 绑定 Zod 校验规则
  });

  // 4. 提交处理函数
  const onSubmit = (data: FormData) => {
    console.log("验证通过的数据:", data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)} style={{ display: 'flex', flexDirection: 'column', gap: '1rem', width: '300px' }}>
      
      {/* 用户名输入框 */}
      <div>
        <label>用户名</label>
        <input {...register('username')} placeholder="输入用户名" />
        {errors.username && <p style={{ color: 'red' }}>{errors.username.message}</p>}
      </div>

      {/* 邮箱输入框 */}
      <div>
        <label>邮箱</label>
        <input {...register('email')} placeholder="输入邮箱" />
        {errors.email && <p style={{ color: 'red' }}>{errors.email.message}</p>}
      </div>

      <button type="submit">提交</button>
    </form>
  );
}

总结

shadcn/ui推荐这套方案不是没有原因的。React Hook Form 解决了"怎么高效收集和管理状态"的问题,而 Zod 解决了"怎么优雅定义规则和类型"的问题。

相关推荐
掘金安东尼1 小时前
本周前端与 AI 技术情报|前端下一步 #462
前端·javascript·面试
赵庆明老师1 小时前
vben开发入门5:vite.config.ts
前端·html·vue3·vben
qq_12084093711 小时前
Three.js 工程向:实例化渲染 InstancedMesh 的批量优化
前端·javascript
起这个名字1 小时前
LangGraphJs 核心概念、工作流程理解及应用
前端·人工智能
小赵同学WoW1 小时前
vue组件基础知识
前端
牛奶1 小时前
浏览器藏了这么多神器,你居然不知道?
前端·chrome·api
WebInfra1 小时前
Rspack 2.0 正式发布!
前端·javascript·前端框架
极速蜗牛2 小时前
Cursor最近变傻了?
前端
码字小学妹2 小时前
Claude Opus 4.7 接入指南(2026):国内配置 + xhigh 推理 + 成本计算
前端
小赵同学WoW2 小时前
插槽【vue2】与 【vue3】对比
前端