在实际开发中,表单往往伴随着复杂的状态管理(字段值、错误信息、触摸状态、提交状态)和繁琐的校验逻辑。Formik 与 Yup 的组合提供了一套声明式、可组合、易维护的表单解决方案,让开发者从手动维护表单状态的苦恼中解脱出来。
Formik 的核心能力
Formik 是一个轻量级的表单状态管理库,它帮你处理:
- 字段值的收集与更新
- 表单校验与错误信息管理
- 字段的访问(脏状态)与触摸状态(touched)
- 提交处理与重置
- 尽量减少不必要的重渲染(通过
Field组件或useFieldHook)
Formik 本质上是一个“受控表单的自动化外壳”,你不再需要为每个输入框编写单独的 useState 和 onChange 处理器。
Yup 的角色:声明式校验
Yup 是一种基于 schema 的校验库,允许你用链式语法描述每个字段的校验规则,生成校验器。它和 Formik 天然集成,Formik 直接接受 Yup schema 作为 validationSchema,自动完成校验并填充错误对象。
快速上手:一个完整的登录表单
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';
// 1. 定义校验 schema(声明式校验)
const loginSchema = Yup.object({
email: Yup.string()
.email('请输入有效的邮箱地址')
.required('邮箱不能为空'),
password: Yup.string()
.min(6, '密码至少6位')
.required('密码不能为空'),
});
// 2. 使用 Formik 组件
function LoginForm() {
const handleSubmit = (values, { setSubmitting }) => {
// 模拟登录请求
console.log('提交数据:', values);
setTimeout(() => {
alert('登录成功');
setSubmitting(false);
}, 1000);
};
return (
<Formik
initialValues={{ email: '', password: '' }}
validationSchema={loginSchema}
onSubmit={handleSubmit}
>
{({ isSubmitting }) => (
<Form>
<div>
<label htmlFor="email">邮箱</label>
<Field type="email" name="email" />
<ErrorMessage name="email" component="div" className="error" />
</div>
<div>
<label htmlFor="password">密码</label>
<Field type="password" name="password" />
<ErrorMessage name="password" component="div" className="error" />
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? '登录中...' : '登录'}
</button>
</Form>
)}
</Formik>
);
}
代码解析:
validationSchema={loginSchema}:当你输入时,Formik 自动运行 Yup 校验,发现错误后立即阻止提交并显示错误信息。<Field>组件自动绑定value、onChange、onBlur,并标记触摸状态。<ErrorMessage>仅在该字段被触摸且存在错误时渲染,避免页面初始就显示一堆红色提示。isSubmitting由 Formik 在onSubmit执行时自动设置为true,返回 Promise 结束后恢复,防止重复提交。
进阶用法:自定义组件与 useField Hook
对于自定义 UI 组件(如设计系统中的 TextInput),可以使用 useField Hook 将其无缝集成进 Formik:
import { useField } from 'formik';
function MyTextInput({ label, ...props }) {
const [field, meta] = useField(props);
return (
<div>
<label htmlFor={props.id || props.name}>{label}</label>
<input {...field} {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</div>
);
}
// 使用
<Formik initialValues={{ name: '' }} ...>
<Form>
<MyTextInput label="用户名" name="name" />
</Form>
</Formik>
这样所有表单字段都可以复用统一的样式和行为,错误提示也完全由 Formik 状态驱动。
Yup 的强大校验能力
Yup 支持丰富的数据类型校验,甚至包括自定义校验:
const schema = Yup.object({
age: Yup.number()
.typeError('必须是数字')
.min(18, '至少18岁')
.required('必填'),
confirmPassword: Yup.string()
.oneOf([Yup.ref('password'), null], '密码不一致')
.required('请确认密码'),
tags: Yup.array().min(1, '至少选择一个标签'),
website: Yup.string().url('请输入合法网址').nullable(),
});
跨字段校验(例如密码确认)通过 Yup.ref 轻松实现。
对比 React Hook Form
| 特性 | Formik + Yup | React Hook Form |
|------|--------------|-----------------|
| 编程范式 | 声明式(JSX 模板) | 声明式 + Hook |
| 性能 | 每次输入会触发顶层 Formik 重渲染(可优化) | 默认非受控,极低重渲染 |
| 上手难度 | 容易,传统 React 思维 | 需要理解 register 机制 |
| 校验集成 | Yup 原生支持 | Yup 通过 resolver 集成 |
| 体积 | 约 15 kB gzip | 约 10 kB gzip |
| 适用场景 | 中小型表单,偏好声明式写法 | 大型复杂表单,追求极致性能 |
如果你团队更习惯“组件即一切”的 React 风格,Formik 会让表单逻辑保持可读性;当表单包含几十上百个字段且需要极致性能时,React Hook Form 是更优选择。
常见坑点与最佳实践
- 避免匿名函数作为
validationSchema参数:
如果每次渲染都重新创建 schema,会导致 Formik 内部的 diff 失效。将 schema 定义在组件外部,或用 useMemo 包裹。
- 字段名与嵌套结构:
Formik 支持点号路径 "address.city",Yup 也能描述嵌套对象校验,保持一致性。
- 异步校验:
Yup schema 支持 .test() 方法进行异步校验(如检查用户名是否已存在),但需要配合 Formik 的 validate 回调进行 debounce,避免频繁请求。
- 减少不必要的重渲染:
可以使用 FastField 替代 Field,它会自动浅比较 props 和 value,避免无关字段变化引起的重渲染。
总结
Formik + Yup 是一套成熟稳定的表单方案,它把表单状态、校验、提交过程全面声明化,让表单代码从繁杂的样板逻辑中解放出来,变得清晰易读。在中小型到中大型项目中,它能显著提升开发效率和代码可维护性,是除 React Hook Form 之外最值得掌握的 React 表单技能。