formik、react-final-form、react-hook-form、react-jsonschema-form 和 redux-form 都是用于在 React 应用中处理表单状态、验证和提交逻辑的库,而 formik-material-ui 是 formik 与 Material-UI 组件的集成桥接包。这些库通过封装表单的复杂性,帮助开发者减少样板代码、提升可维护性,并支持复杂的验证、异步提交和字段联动等场景。它们在状态管理方式、性能优化策略、API 设计哲学以及与 UI 库的集成能力上存在显著差异。
在 React 开发中,表单处理看似简单,实则暗藏复杂性 —— 状态同步、验证反馈、错误处理、性能优化等问题常让开发者陷入泥潭。本文从真实工程视角,深入对比 formik、formik-material-ui、react-final-form、react-hook-form、react-jsonschema-form 和 redux-form 六个主流方案,助你做出精准技术选型。
redux-form 已被官方标记为废弃(deprecated)。其 GitHub 仓库和 npm 页面均明确建议迁移到其他方案。原因在于它将整个表单状态存入 Redux store,导致:
// redux-form (已废弃,勿用于新项目)
import { Field, reduxForm } from 'redux-form';
const MyForm = ({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="email" component="input" type="email" />
</form>
);
export default reduxForm({ form: 'myForm' })(MyForm);
formik-material-ui 也已停止维护。其 npm 页面注明 "This library is no longer maintained"。它仅为 Formik 与旧版 Material-UI(v4)的胶水代码,新项目应直接使用 MUI v5 的 TextField 等组件手动绑定 Formik 或切换至更现代的方案。
// formik-material-ui (已废弃)
import { TextField } from 'formik-material-ui';
import { Field } from 'formik';
<Field component={TextField} name="username" />;
结论:新项目应完全排除
redux-form和formik-material-ui。
formik 和 react-final-form 默认采用受控模式 —— 表单值由 React state 驱动,每次输入都更新 state 并触发重渲染。
// Formik (受控模式)
import { useFormik } from 'formik';
function MyForm() {
const formik = useFormik({
initialValues: { email: '' },
onSubmit: values => alert(JSON.stringify(values)),
});
return (
<form onSubmit={formik.handleSubmit}>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
<button type="submit">Submit</button>
</form>
);
}
// React Final Form (受控模式示例)
import { Form, Field } from 'react-final-form';
const MyForm = () => (
<Form
onSubmit={values => console.log(values)}
render={({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="email">
{({ input }) => <input {...input} type="email" />}
</Field>
<button type="submit">Submit</button>
</form>
)}
/>
);
react-hook-form 默认采用非受控模式 —— 表单值由 DOM 自身管理,仅在需要时(如提交、验证)读取,极大减少重渲染。
// React Hook Form (非受控模式)
import { useForm } from 'react-hook-form';
function MyForm() {
const { register, handleSubmit } = useForm();
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email')} type="email" />
<button type="submit">Submit</button>
</form>
);
}
性能影响:在大型表单(>20 字段)中,非受控模式可减少 50%+ 的重渲染次数。
formik 提供两种 API:Render Props(<Formik> 组件)和自定义 Hook(useFormik),偏向声明式。react-final-form 主要通过 Render Props 和 HOC,强调函数式组合。react-hook-form 完全基于 Hooks,API 极简,符合现代 React 开发习惯。所有活跃库均支持同步/异步验证,但集成方式不同。
// Formik + Yup
import * as Yup from 'yup';
const validationSchema = Yup.object({
email: Yup.string().email().required(),
});
const formik = useFormik({
validationSchema,
// ...
});
// React Final Form + 自定义验证函数
const validate = values => {
const errors = {};
if (!values.email) errors.email = 'Required';
return errors;
};
<Form validate={validate} />
// React Hook Form + Yup (通过 resolver)
import { yupResolver } from '@hookform/resolvers/yup';
const { register } = useForm({
resolver: yupResolver(validationSchema),
});
注意:
react-hook-form的验证默认在提交时触发,可通过mode选项改为实时验证(onChange/onBlur)。
处理复杂数据结构时,各库能力差异明显。
// Formik 处理数组
{formik.values.friends.map((_, index) => (
<input
key={index}
{...formik.getFieldProps(`friends[${index}].name`)}/>
))}
// React Final Form 使用 FieldArray
import { FieldArray } from 'react-final-form-arrays';
<FieldArray name="friends">
{({ fields }) => (
fields.map((name, index) => (
<Field name={`${name}.name`} component="input" />
))
)}
</FieldArray>
// React Hook Form 使用 useFieldArray
import { useFieldArray } from 'react-hook-form';
const { fields, append } = useFieldArray({ control, name: 'friends' });
{fields.map((field, index) => (
<input key={field.id} {...register(`friends.${index}.name`)} />
))}
经验之谈:
react-hook-form的useFieldArray在动态增删字段时性能最优,因其内部使用非受控策略。
现代项目多使用 UI 组件库,集成便捷性至关重要。
// Formik + MUI v5 (手动绑定)
import { TextField } from '@mui/material';
<TextField
{...formik.getFieldProps('email')}
error={!!formik.errors.email}
helperText={formik.errors.email}
/>
// React Hook Form + MUI v5
<Controller
name="email"
control={control}
render={({ field, fieldState }) => (
<TextField
{...field}
error={!!fieldState.error}
helperText={fieldState.error?.message}
/>
)}
/>
关键区别:
react-hook-form对于受控组件(如 MUI 的TextField)需使用Controller包装,而formik可直接通过getFieldProps绑定。但react-hook-form的register可直接用于原生 input,更轻量。
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 新项目启动 | react-hook-form | 性能最优、API 简洁、TypeScript 支持完善、社区活跃 |
| 已有 Formik 项目 | 继续使用 formik | 迁移成本高,除非遇到性能瓶颈 |
| JSON Schema 驱动表单 | react-jsonschema-form | 自动生成 UI,适合低代码/配置化场景 |
| 超大型动态表单 | react-final-form | 订阅机制精细,可优化特定字段重渲染 |
| 遗留 Redux 系统 | 评估迁移至 react-hook-form | redux-form 已废弃,长期维护风险高 |
react-hook-form:它代表了现代 React 表单处理的最佳实践 —— 性能、简洁性、灵活性兼备。redux-form 和 formik-material-ui 不应出现在新代码库中。react-jsonschema-form 能极大提升效率;若团队已深度 invested in Formik 且无性能问题,可暂不迁移。表单虽小,却关乎用户体验与应用性能。选对工具,事半功倍。
选择 react-hook-form 如果你追求极致性能(默认非受控模式减少重渲染)、简洁的 Hook API 和出色的 TypeScript 体验。它特别适合现代 React 应用,支持受控与非受控混合模式,验证灵活(可集成 Yup、Zod 等),是当前主流推荐方案,尤其适用于新项目。
选择 formik 如果你偏好基于 Render Props 或自定义 Hook 的声明式 API,且项目使用受控组件模式。它适合中等复杂度的表单,提供良好的 TypeScript 支持和丰富的社区生态。但需注意其默认对整个表单进行重渲染,对于大型表单可能需要配合 useMemo 或 Field 组件优化性能。
选择 react-final-form 如果你需要高性能的订阅机制(仅重渲染变更字段)、支持复杂嵌套表单结构,或偏好函数式组件与高阶组件(HOC)结合的 API。它基于 Final Form 引擎,提供细粒度控制,适合大型或动态表单,但学习曲线略陡,且社区活跃度近年有所下降。
选择 formik-material-ui 仅当你已在项目中同时使用 formik 和 Material-UI(v4),并希望快速将 Formik 的状态绑定到 MUI 输入组件。该库仅为适配层,不提供独立表单逻辑,且官方已明确停止维护,新项目应避免使用,建议直接使用 MUI v5 自带的 @mui/x-date-pickers 等集成方案或手动绑定。
选择 react-jsonschema-form 如果你的表单结构由 JSON Schema 动态驱动(如低代码平台、配置化表单生成器)。它能根据 schema 自动生成 UI,大幅减少手写表单代码,但定制 UI 或复杂交互时灵活性受限,适合 schema 驱动场景而非通用表单开发。
选择 redux-form 仅当你维护一个遗留系统且已深度耦合 Redux 状态树。该库已官方标记为不再维护(deprecated),新项目绝对不应采用。其将整个表单状态存入 Redux store,导致不必要的全局状态膨胀和性能开销,现代替代方案(如 react-hook-form)在架构上更优。
Get started | API | Form Builder | FAQs | Examples
npm install react-hook-form
import { useForm } from 'react-hook-form';
function App() {
const {
register,
handleSubmit,
formState: { errors },
} = useForm();
return (
<form onSubmit={handleSubmit((data) => console.log(data))}>
<input {...register('firstName')} />
<input {...register('lastName', { required: true })} />
{errors.lastName && <p>Last name is required.</p>}
<input {...register('age', { pattern: /\d+/ })} />
{errors.age && <p>Please enter a number for age.</p>}
<input type="submit" />
</form>
);
}
We’re incredibly grateful to these kind and generous sponsors for their support!
| Follower24 |
|---|
| Pennylane | Thanks.dev | Kinsta | Niche | Principal | Hygraph |
|---|---|---|---|---|---|
![]() | ![]() | ![]() |
Thank you to our previous sponsors for your generous support!
Thanks go to all our backers! [Become a backer].
Thanks go to these wonderful people! [Become a contributor].
Documentation website supported and backed by Vercel