final-form、formik、react-hook-form 和 redux-form 都是用于在 React 应用中管理表单状态、验证和提交逻辑的库。它们的目标是简化表单开发,减少样板代码,并提供一致的用户体验。redux-form 依赖 Redux 状态管理,formik 提供了基于组件的封装,final-form 是一个与框架无关的核心引擎,而 react-hook-form 则充分利用 React Hooks 实现高性能表单处理。
在 React 开发中,表单处理看似简单,实则涉及状态同步、验证、错误反馈、性能优化等复杂问题。final-form、formik、react-hook-form 和 redux-form 各自提供了不同的解决思路。本文从真实工程角度出发,深入比较它们的核心机制、性能特征和适用场景。
首先明确:redux-form 已被官方弃用。其 npm 页面和 GitHub 仓库均标注为 deprecated,建议仅用于遗留系统维护,新项目务必选择其他方案。其余三个库目前均处于活跃维护状态。
redux-form:全局状态驱动(已弃用)redux-form 将整个表单状态存储在 Redux store 中。每个字段的值、是否触碰、是否脏等信息都作为全局状态的一部分。这导致任何字段变更都会触发整个 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:组件状态封装formik 将表单状态封装在组件内部(通过 useState 或类组件 state),并通过 Formik 组件或 useFormik Hook 暴露 API。它采用受控组件模式,所有字段值由 Formik 管理,通过 Field 组件或 getFieldProps 注入 props。
// formik
import { useFormik } from 'formik';
const MyForm = () => {
const formik = useFormik({
initialValues: { email: '' },
onSubmit: values => alert(JSON.stringify(values, null, 2))
});
return (
<form onSubmit={formik.handleSubmit}>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
</form>
);
};
final-form:订阅式状态引擎final-form 本身不绑定 React,它是一个纯 JavaScript 表单状态管理引擎。通过 react-final-form 包提供 React 绑定。其核心是“订阅”机制:只有订阅了特定字段状态的组件才会在该字段变化时重渲染。
// final-form
import { Form, Field } from 'react-final-form';
const MyForm = () => (
<Form
onSubmit={values => alert(JSON.stringify(values, null, 2))}
render={({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<Field name="email">
{({ input }) => <input {...input} type="email" />}
</Field>
</form>
)}
/>
);
react-hook-form:非受控优先 + 智能注册react-hook-form 默认采用非受控组件(uncontrolled components)模式,通过 register 方法将原生 DOM 元素注册到表单状态管理器。它只在必要时(如验证失败)才接管字段值,从而避免不必要的重渲染。
// react-hook-form
import { useForm } from 'react-hook-form';
const MyForm = () => {
const { register, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(values => alert(JSON.stringify(values, null, 2)))}>
<input {...register('email')} type="email" />
</form>
);
};
redux-form:任何字段变更触发整个 Redux store 更新,可能导致全应用重渲染(除非精心优化 selector)。formik:默认情况下,任何字段变更会触发整个 Formik 组件及其子树重渲染。可通过 useField 或 Field 的细粒度订阅缓解,但需额外注意。final-form:通过 subscription 选项,可精确控制哪些状态变化触发重渲染。例如,只订阅 submitting 状态的按钮不会因字段输入而重渲染。react-hook-form:默认无重渲染 —— 字段值直接读取 DOM,仅在验证或提交时同步状态。使用 watch() 或 Controller 时才会引入响应式更新。假设有一个包含 10 个字段的表单,仅修改第一个字段:
formik(未优化):
// 所有字段都会重渲染
const formik = useFormik({ initialValues: { field1: '', /* ... */ } });
return (
<>
<input {...formik.getFieldProps('field1')} />
{/* 其他9个字段也会因 formik.values 变更而重渲染 */}
</>
);
final-form(优化后):
// 仅 field1 的 Field 组件重渲染
<Field name="field1">{({ input }) => <input {...input} />}</Field>
// 其他 Field 组件不受影响
react-hook-form(默认):
// 无任何 React 重渲染,值直接从 DOM 读取
<input {...register('field1')} />
// 其他字段完全不受影响
所有库都支持同步验证函数:
formik:
validate: values => {
const errors = {};
if (!values.email) errors.email = 'Required';
return errors;
}
final-form:
// 在 Field 上设置 validate
<Field name="email" validate={value => !value ? 'Required' : undefined} />
react-hook-form:
// 通过 register 的 validate 选项
<input {...register('email', {
required: 'Required',
validate: value => value.includes('@') || 'Invalid email'
})} />
formik 支持 validate 返回 Promise,但需注意防抖和取消逻辑需自行处理。final-form 的 validate 函数可返回 Promise,内置防抖(通过 asyncValidate 配置)。react-hook-form 的 validate 函数可返回 Promise,但同样需自行处理竞态条件(如使用 resolver 集成 yup 或 zod 可简化)。formik:通过 FieldArray 组件支持,API 直观但可能因数组变更导致大面积重渲染。
<FieldArray name="friends">
{({ push, remove, form }) => (
<div>
{form.values.friends.map((_, index) => (
<input {...form.getFieldProps(`friends[${index}]`)} />
))}
<button onClick={() => push('')}>Add</button>
</div>
)}
</FieldArray>
final-form:需手动管理数组状态,但可通过 useField 和自定义逻辑实现,重渲染更可控。
react-hook-form:提供 useFieldArray Hook,专为动态字段设计,性能优秀。
const { fields, append, remove } = useFieldArray({ name: 'friends' });
return (
<div>
{fields.map((field, index) => (
<input key={field.id} {...register(`friends.${index}`)} />
))}
<button onClick={() => append('')}>Add</button>
</div>
);
formik:通过 setFieldValue 在 onChange 中手动更新关联字段。final-form:利用 mutators 或在 onChange 中调用 form.change。react-hook-form:使用 setValue 更新关联字段,配合 watch 监听变化。formik:常与 yup 配合,通过 validationSchema 选项。final-form:可通过 final-form-calculate 或自定义 validate 集成 yup/joi。react-hook-form:官方推荐 resolver 选项,无缝集成 yup、zod、joi 等。
const { register } = useForm({
resolver: yupResolver(schema)
});
formik:提供 debug prop 输出当前状态,DevTools 支持良好。final-form:可通过 debug 选项输出内部状态变化。react-hook-form:提供 devTool 插件,实时监控表单状态。| 特性 | final-form | formik | react-hook-form | redux-form |
|---|---|---|---|---|
| 状态管理 | 订阅式引擎 | 组件内状态 | 非受控 + 注册 | Redux 全局状态 |
| 重渲染控制 | 精细(按字段订阅) | 默认粗粒度(可优化) | 极少(默认无) | 全局(需优化) |
| 学习曲线 | 中(需理解订阅) | 低(组件化) | 中(非受控思维) | 高(Redux 依赖) |
| 动态字段 | 手动实现 | FieldArray | useFieldArray | FieldArray |
| 验证集成 | 灵活 | yup 为主 | 多库 resolver | 灵活 |
| 维护状态 | 活跃 | 活跃 | 活跃 | 已弃用 |
react-hook-form:性能最优,API 现代,社区活跃,适合大多数场景。formik:快速原型开发、团队熟悉 render props 模式时效率高。final-form:大型表单、高频更新场景,愿意投入更多集成成本。redux-form:技术债风险高,性能瓶颈明显,社区已迁移。最终,没有“最好”的库,只有“最适合”当前项目约束和团队习惯的方案。理解它们背后的设计权衡,才能做出明智的技术决策。
选择 final-form 如果你需要一个轻量、与框架无关的表单状态管理核心,且愿意自行封装 React 绑定或已有自定义集成方案。它适合对性能敏感、需要精细控制订阅粒度的场景,但要求开发者处理更多底层细节,如字段注册和变更监听。
选择 formik 如果你偏好基于组件的声明式 API,希望快速上手并获得开箱即用的表单功能(如验证、错误处理、嵌套字段)。它适合中小型项目或团队熟悉 React 组件模式,但需注意其基于 render props 的设计可能导致不必要的重渲染。
选择 react-hook-form 如果你追求极致性能、最小化重渲染,并希望利用 React Hooks 的现代开发模式。它通过非受控组件和智能字段注册显著减少运行时开销,适合大型复杂表单或对性能有严格要求的应用,但需适应其非受控为主的哲学。
不要在新项目中使用 redux-form,因为它已被官方标记为不再维护(deprecated)。现有项目应计划迁移到其他替代方案。Redux 社区已转向更轻量、专用的表单解决方案,避免将表单状态全局化带来的性能和维护成本。
You build great forms, but do you know HOW users use your forms? Find out with Form Nerd! Professional analytics from the creator of Final Form.
💰 Hey there! Do you fancy yourself a javascript expert? Take this quiz and get offers from top tech companies! 💰
✅ Zero dependencies
✅ Framework agnostic
✅ Opt-in subscriptions - only update on the state you need!
✅ 💥 5.1k gzipped 💥
Comprehensive JS framework and UI components for building enterprise-grade web apps.
In the interest of making 🏁 Final Form the best library it can be, we'd love your thoughts and feedback.
