表单处理方案:React Hook Form vs Formik

3 阅读2分钟

引言

在 React 应用中,表单处理是最常见也是最复杂的任务之一。从简单的登录表单到复杂的多步骤数据录入,我们需要处理状态管理、验证、错误提示、提交逻辑等多个方面。

目前 React 生态中最流行的两个表单库是 React Hook FormFormik。本文将深入对比这两个方案,帮助你做出合适的技术选型。

React Hook Form:性能优先的选择

React Hook Form 是一个轻量级(仅 8.7KB)的表单库,核心理念是减少不必要的重渲染。它通过非受控组件和 Ref 的方式管理表单状态,避免了每次输入都触发组件重新渲染。

核心特性

  • 零依赖:不依赖任何第三方库
  • 高性能:最小化重渲染次数
  • 简单易用:API 设计符合 React Hooks 思维
  • 灵活验证:支持内置验证和外部验证库(如 Zod、Yup)

实战示例

import { useForm } from 'react-hook-form';

function LoginForm() {
  const { 
    register, 
    handleSubmit, 
    formState: { errors, isSubmitting } 
  } = useForm({
    mode: 'onChange' // 实时验证
  });

  const onSubmit = async (data) => {
    console.log('表单数据:', data);
    // 模拟 API 调用
    await new Promise(resolve => setTimeout(resolve, 1000));
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>邮箱</label>
        <input 
          {...register('email', {
            required: '邮箱不能为空',
            pattern: {
              value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+.[A-Z]{2,}$/i,
              message: '邮箱格式不正确'
            }
          })}
        />
        {errors.email && <span>{errors.email.message}</span>}
      </div>

      <div>
        <label>密码</label>
        <input 
          type="password"
          {...register('password', {
            required: '密码不能为空',
            minLength: {
              value: 6,
              message: '密码至少 6 '
            }
          })}
        />
        {errors.password && <span>{errors.password.message}</span>}
      </div>

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? '登录中...' : '登录'}
      </button>
    </form>
  );
}

Formik:功能全面的解决方案

Formik 是一个更早出现的表单库,提供了完整的表单状态管理方案。它采用受控组件的方式,API 设计更加结构化。

核心特性

  • 完整状态管理:values、errors、touched、isSubmitting 等
  • 内置验证支持:原生支持 Yup 验证
  • TypeScript 友好:完善的类型定义
  • 社区生态成熟:大量插件和示例

实战示例

import { useFormik } from 'formik';
import * as Yup from 'yup';

function LoginForm() {
  const formik = useFormik({
    initialValues: {
      email: '',
      password: ''
    },
    validationSchema: Yup.object({
      email: Yup.string()
        .required('邮箱不能为空')
        .email('邮箱格式不正确'),
      password: Yup.string()
        .required('密码不能为空')
        .min(6, '密码至少 6 位')
    }),
    onSubmit: async (values) => {
      console.log('表单数据:', values);
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  });

  return (
    <form onSubmit={formik.handleSubmit}>
      <div>
        <label>邮箱</label>
        <input
          name="email"
          type="email"
          onChange={formik.handleChange}
          onBlur={formik.handleBlur}
          value={formik.values.email}
        />
        {formik.touched.email && formik.errors.email && (
          <span>{formik.errors.email}</span>
        )}
      </div>

      <div>
        <label>密码</label>
        <input
          name="password"
          type="password"
          onChange={formik.handleChange}
          onBlur={formik.handleBlur}
          value={formik.values.password}
        />
        {formik.touched.password && formik.errors.password && (
          <span>{formik.errors.password}</span>
        )}
      </div>

      <button type="submit" disabled={formik.isSubmitting}>
        {formik.isSubmitting ? '登录中...' : '登录'}
      </button>
    </form>
  );
}

核心对比

维度React Hook FormFormik
包体积8.7KB15.2KB
性能优秀(非受控)良好(受控)
学习曲线平缓中等
验证方案灵活(内置/Yup/Zod)内置 Yup 支持
TypeScript支持优秀支持
社区生态快速增长成熟稳定

进阶用法对比

动态表单字段

React Hook Form 使用 useFieldArray 处理动态字段:

import { useFieldArray } from 'react-hook-form';

function DynamicForm() {
  const { register, control, handleSubmit } = useForm();
  const { fields, append, remove } = useFieldArray({
    control,
    name: 'items'
  });

  return (
    <form onSubmit={handleSubmit(console.log)}>
      {fields.map((field, index) => (
        <div key={field.id}>
          <input {...register(`items.${index}.name`)} />
          <button type="button" onClick={() => remove(index)}>
            删除
          </button>
        </div>
      ))}
      <button type="button" onClick={() => append({ name: '' })}>
        添加
      </button>
    </form>
  );
}

Formik 使用数组操作:

function DynamicForm() {
  const formik = useFormik({
    initialValues: { items: [{ name: '' }] },
    onSubmit: console.log
  });

  return (
    <form onSubmit={formik.handleSubmit}>
      {formik.values.items.map((_, index) => (
        <div key={index}>
          <input
            name={`items.${index}.name`}
            value={formik.values.items[index].name}
            onChange={formik.handleChange}
          />
          <button
            type="button"
            onClick={() => {
              const newItems = [...formik.values.items];
              newItems.splice(index, 1);
              formik.setFieldValue('items', newItems);
            }}
          >
            删除
          </button>
        </div>
      ))}
      <button
        type="button"
        onClick={() =>
          formik.setFieldValue('items', [
            ...formik.values.items,
            { name: '' }
          ])
        }
      >
        添加
      </button>
    </form>
  );
}

选型建议

选择 React Hook Form 的场景

  1. 性能敏感:大型表单、频繁更新的场景
  2. 轻量优先:关注包体积的项目
  3. 简单验证:不需要复杂验证逻辑
  4. 新项目:没有历史包袱

选择 Formik 的场景

  1. 复杂验证:需要 Yup 的强大验证能力
  2. TypeScript 项目:需要完善的类型支持
  3. 企业级应用:需要成熟的生态支持
  4. 团队熟悉:团队已有 Formik 使用经验

总结

React Hook Form 和 Formik 都是优秀的表单解决方案,没有绝对的优劣之分:

  • React Hook Form 以性能为核心,适合追求极致体验的现代应用
  • Formik 以功能完整性见长,适合需要全面解决方案的企业项目

在实际项目中,建议根据团队技术栈、项目规模和性能要求进行选择。对于大多数新项目,React Hook Form 是更现代化的选择;而对于需要复杂验证和 TypeScript 支持的项目,Formik 仍然是可靠的选择。

无论选择哪个库,良好的表单设计都应该关注用户体验:清晰的错误提示、合理的验证时机、友好的交互反馈,这些比技术选型本身更重要。