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 解决了"怎么优雅定义规则和类型"的问题。

相关推荐
坐吃山猪2 小时前
React+TypeScript Agent开发规范
前端·react.js·typescript
①条咸鱼2 小时前
React 项目运用 RxJS 设置节流
react.js
SuperEugene2 小时前
Vue3 表格封装实战:列配置 + slot 扩展 + 请求生命周期|Vue生态精选篇
前端·javascript·vue.js·前端框架
小鹿软件办公2 小时前
Firefox Nova:火狐浏览器即将迎来大规模改版
前端·firefox
Lee_Yu_Fan2 小时前
在vue3 + ElementUI 项目中自定义 Icon
前端·elementui
吴声子夜歌2 小时前
小程序——转发API
java·前端·小程序
Never_Satisfied2 小时前
在JavaScript / HTML中,获取指定元素的父元素
开发语言·javascript·html