毫无疑问,网页表单在我们的网站或应用程序中发挥着不可或缺的作用。默认情况下,它们提供了一套有用的元素和功能——从图例和字段集到原生验证和状态——但是当我们开始考虑使用它们的特殊性时,它们只能带我们走到这里。例如,我们如何操作表单的状态?不同形式的验证呢?即使将表单连接到发布提交有时也是一项艰巨的工作。
像 React 这样的组件驱动的前端库可以简化连接网页表单的任务,但也可能变得冗长且重复。这就是为什么我想向您介绍 Formik,这是一个小型库,它解决了在 React 中编写表单时最令人烦恼的三个部分
- 状态操作
- 表单验证(和错误消息)
- 表单提交
我们将在本文中一起构建一个表单。我们将从一个 React 组件开始,然后集成 Formik,同时演示它处理状态、验证和提交的方式。
将表单创建为 React 组件
组件通过它们的状态和属性生存和呼吸。HTML 表单元素与 React 组件的共同点是它们自然会保留一些内部状态。它们的值也会自动存储在它们的 value 属性中。
允许表单元素在 React 中管理自己的状态使其成为 非受控 组件。这只是说 DOM 处理状态而不是 React 的一种花哨的说法。虽然这有效,但使用 受控 组件通常更容易,其中 React 处理状态并充当唯一的事实来源,而不是 DOM。
一个简单的 HTML 表单的标记可能如下所示
<form>
<div className="formRow">
<label htmlFor="email">Email address</label>
<input type="email" name="email" className="email" />
</div>
<div className="formRow">
<label htmlFor="password">Password</label>
<input type="password" name="password" className="password" />
</div>
<button type="submit">Submit</button>
</form>
我们可以将其转换为受控的 React 组件,如下所示
function HTMLForm() {
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
return (
<form>
<div className="formRow">
<label htmlFor="email">Email address</label>
<input
type="email"
name="email"
className="email"
value={email}
onChange={e => setEmail(e.target.value)}
/>
</div>
<div className="formRow">
<label htmlFor="password">Password</label>
<input
type="password"
name="password"
className="password"
value={password}
onChange={e => setPassword(e.target.value)}
/>
</div>
<button type="submit">Submit</button>
</form>
);
}
这有点冗长,但它带来了一些好处
- 我们在状态中获得了表单值的唯一事实来源。
- 我们可以在任何时候以及我们想要的方式验证表单。
- 通过加载我们需要的以及何时需要的内容,我们获得了性能优势。
好的,那么为什么再次使用 Formik 呢?
与任何 JavaScript 一样,那里已经存在大量表单管理库,例如 React Hook Form 和 Redux Form,我们可以使用它们。但是,有一些事情使 Formik 从众多库中脱颖而出
- 它是声明式的:Formik 通过抽象并负责状态、验证和提交来消除冗余。
- 它提供了一个逃生舱:抽象很好,但表单对某些模式来说很特殊。Formik 为您进行抽象,但也让您在需要时可以控制它。
- 它将表单状态放在一起:Formik 将与您的表单相关的所有内容都保存在您的表单组件中。
- 它是适应性强的:Formik 不会强加任何规则给您。您可以根据需要使用尽可能少或尽可能多的 Formik。
- 易于使用:Formik 只需工作。
听起来不错?让我们将 Formik 实现到我们的表单组件中。
使用 Formik
我们将构建一个基本的登录表单,以了解基础知识。我们将涉及三种不同的使用 Formik 的方法
我创建了一个演示,其中包含我们需要的包,Formik 和 Yup。
方法 1:使用 useFormik 钩子
就目前而言,我们的表单没有任何实际作用。要开始使用 Formik,我们需要导入useFormik
钩子。当我们使用该钩子时,它会返回所有帮助我们管理表单的 Formik 函数和变量。如果我们将返回的值记录到控制台,我们会得到以下内容

我们将调用useFormik
并将其传递initialValues
以开始。然后,当表单提交发生时,onSubmit
处理程序会触发。以下是其外观
// This is a React component
function BaseFormik() {
const formik = useFormik({
initialValues: {
email: "",
password: ""
},
onSubmit(values) {
// This will run when the form is submitted
}
});
// If you're curious, you can run this Effect
// useEffect(() => {
// console.log({formik});
// }, [])
return (
// Your actual form
)
}
然后我们将 Formik 绑定到我们的表单元素
// This is a React component
function BaseFormik() {
const formik = useFormik({
initialValues: {
email: "",
password: ""
},
onSubmit(values) {
// This will run when the form is submitted
}
});
// If you're curious, you can run this Effect
// useEffect(() => {
// console.log({formik});
// }, [])
return (
// We bind "onSubmit" to "formik.handleSubmit"
<form className="baseForm" onSubmit={formik.handleSubmit} noValidate>
<input
type="email"
name="email"
id="email"
className="email formField"
value={formik.values.email} // We also bind our email value
onChange={formik.handleChange} // And, we bind our "onChange" event.
/>
</form>
)
}
绑定方式如下
- 它使用
onSubmit={formik.handleSubmit}
处理表单提交。 - 它使用
value={formik.values.email}
和onChange={formik.handleChange}
处理输入的状态。
如果您仔细观察,我们不必设置我们的状态,也不必像通常使用 React 时那样处理onChange
或onSubmit
事件。
但是,您可能已经注意到,我们的表单包含一些冗余。我们必须深入到 formik 中并手动绑定表单输入的value
和onChange
事件。这意味着我们应该解构返回值并立即将必要的属性绑定到相关字段,如下所示
// This is a React component
function BaseFormik() {
const {getFieldProps, handleSubmit} = useFormik({
initialValues: {
email: "",
password: ""
},
onSubmit(values) {
// This will run when the form is submitted
}
});
// If you're curious, you can run this Effect
// useEffect(() => {
// console.log({formik});
// }, [])
return (
<form className="baseForm" onSubmit={handleSubmit} noValidate>
<input
type="email"
id="email"
className="email formField"
{...getFieldProps("email")} // We pass the name of the dependent field
/>
</form>
)
}
让我们使用包含的<Formik/>
组件进一步推进。
方法 2:使用 Formik 和 React 上下文
<Formik/>
组件公开了各种其他组件,这些组件增加了更多抽象和合理的默认值。例如,<Form/
>、<Field/>
和<ErrorMessage/>
等组件可以直接使用。
请记住,在使用<Formik/>
时,您不必使用这些组件,但使用它们时确实需要<Formik/>
(或withFormik
)。
使用<Formik/>
需要进行彻底的修改,因为它使用的是 渲染 props 模式,而不是使用useFormik
的钩子。渲染 props 模式在 React 中并不是什么新鲜事物。它是一种能够在组件之间实现代码可重用性的模式——这是钩子更好地解决的问题。但是,<Formik/>
拥有大量自定义组件,可以使表单操作变得更加容易。
import { Formik } from "formik";
function FormikRenderProps() {
const initialValues = {
email: "",
password: ""
};
function onSubmit(values) {
// Do stuff here...
alert(JSON.stringify(values, null, 2));
}
return (
<Formik {...{ initialValues, onSubmit }}>
{({ getFieldProps, handleSubmit }) => (
<form className="baseForm" onSubmit={handleSubmit} noValidate>
<input
type="email"
id="email"
className="email formField"
{...getFieldProps("email")}
/>
</form>
)}
</Formik>
);
}
请注意,initialValues
和onSubmit
已完全从useFormik
中分离。这意味着我们可以传递<Formik/>
需要的属性,特别是initialValues
和useFormik
。
<Formik/>
返回一个已解构为getFieldProps
和handleSubmit
的值。其他所有内容基本上与使用useFormik
的第一种方法相同。
如果您感觉有点生疏,这里是对 React 渲染 props 的回顾。
我们实际上还没有使用任何<Formik/>
组件。我故意这样做是为了证明 Formik 的适应性。我们当然希望将这些组件用于我们的表单字段,所以让我们重写该组件,使其使用<Form/>
组件。
import { Formik, Field, Form } from "formik";
function FormikRenderProps() {
const initialValues = {
email: "",
password: ""
};
function onSubmit(values) {
// Do stuff here...
alert(JSON.stringify(values, null, 2));
}
return (
<Formik {...{ initialValues, onSubmit }}>
{() => (
<Form className="baseForm" noValidate>
<Field
type="email"
id="email"
className="email formField"
name="email"
/>
</Form>
)}
</Formik>
);
}
我们将<form/>
替换为<Form/>
,并删除了onSubmit
处理程序,因为 Formik 为我们处理了该程序。请记住,它承担了处理表单的所有责任。
我们还将<input/>
替换为<Field/>
并删除了绑定。同样,Formik 也处理了这一点。
现在也不需要再处理<Formik/>
的返回值了。您猜对了,Formik 也处理了这一点。
Formik 为我们处理所有事情。现在,我们可以更多地关注表单的业务逻辑,而不是那些本质上可以进行抽象的事情。
我们几乎可以开始了,您猜怎么着?我们一直没有关注状态管理或表单提交!
“验证怎么办?”您可能会问。我们还没有涉及到这一点,因为这本身就是一个全新的层次。在跳到最后一种方法之前,让我们先谈谈这一点。
使用 Formik 进行表单验证
如果您曾经使用过表单(我敢肯定您用过),那么您就会意识到验证不是可以忽略的事情。
我们希望能够控制何时以及如何进行验证,以便创造新的机会来打造更好的用户体验。例如,Gmail 只有在电子邮件地址输入被验证并通过身份验证后才会允许您输入密码。我们还可以进行即时验证,并在不进行额外交互或页面刷新的情况下显示消息。
以下三种方法是 Formik 处理验证的方式
- 在表单级别
- 在字段级别
- 使用手动触发器
表单级别的验证意味着将表单作为一个整体进行验证。由于我们可以立即访问表单值,因此可以通过以下两种方式立即验证整个表单:
- 使用
validate
,或者 - 使用第三方库和
validationSchema
。
validate
和 validationSchema
都是返回 errors
对象的函数,该对象包含与 initialValues
相同的键值对。我们可以将它们传递给 useFormik
、<Formik/>
或 withFormik
。
validate
用于自定义验证,而 validationSchema
与 Yup 等第三方库一起使用。
这是一个使用 validate
的示例
// Pass the `onSubmit` function that gets called when the form is submitted.
const formik = useFormik({
initialValues: {
email: "",
password: ""
},
// We've added a validate function
validate() {
const errors = {};
// Add the touched to avoid the validator validating all fields at once
if (formik.touched.email && !formik.values.email) {
errors.email = "Required";
} else if (
!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(formik.values.email)
) {
errors.email = "Invalid email address";
}
if (formik.touched.password && !formik.values.password) {
errors.password = "Required";
} else if (formik.values.password.length <= 8) {
errors.password = "Must be more than 8 characters";
}
return errors;
},
onSubmit(values) {
// Do stuff here...
}
});
// ...
接下来,我们来看一个使用 validationSchema
的示例
const formik = useFormik({
initialValues: {
email: "",
password: ""
},
// We used Yup here.
validationSchema: Yup.object().shape({
email: Yup.string()
.email("Invalid email address")
.required("Required"),
password: Yup.string()
.min(8, "Must be more than 8 characters")
.required("Required")
}),
onSubmit(values) {
// Do stuff here...
}
});

在字段级别进行验证或使用手动触发器都比较容易理解。尽管如此,您仍然很可能在大多数情况下使用表单级别的验证。也值得查看一下文档,以了解其他用例。
方法 3:使用 withFormik 作为高阶组件
withFormik
是一个高阶组件,如果需要,可以以这种方式使用它。编写表单,然后通过 Formik 公开它。
一些实际示例
到目前为止,我们已经了解了 Formik,涵盖了使用它在 React 中创建表单的好处,并介绍了几种将其作为 React 组件实现的方法,同时演示了我们可以使用它进行验证的各种方式。我们还没有查看这些关键概念的示例。
因此,让我们来看几个实际应用:显示错误消息和根据电子邮件输入的内容生成用户名。
显示错误消息
我们已经构建了表单并对其进行了验证。我们还捕获了一些可以在 errors
对象中找到的错误。但是,如果我们实际上没有显示这些错误,那么就没有用。
Formik 使这项任务变得非常简单。我们只需要检查任何我们已经查看过的方法(<Formik/>
、useFormik
或 withFormik
)返回的 errors
对象,并显示它们即可。
<label className="formFieldLabel" htmlFor="email">
Email address
<span className="errorMessage">
{touched["email"] && errors["email"]}
</span>
</label>
<div className="formFieldWrapInner">
<input
type="email"
id="email"
className="email formField"
{...getFieldProps("email")}
/>
</div>
如果验证过程中出现错误,{touched["email"] && errors["email"]}
将会向用户显示错误信息。
我们也可以使用 <ErrorMessage/>
做同样的事情。使用它,我们只需要告诉它要监视的依赖字段的名称即可。
<ErrorMessage name="email">
{errMsg => <span className="errorMessage">{errMsg}</span>}
</ErrorMessage>
从电子邮件地址生成用户名
想象一个表单,它可以根据用户的电子邮件地址自动为用户生成用户名。换句话说,用户在电子邮件输入框中输入的内容会被提取出来,删除 @
和其后的所有内容,并留下剩下的内容作为用户名。
例如:[email protected]
生成 @jane
。
Formik 公开了可以“拦截”其功能并让我们执行某些效果的辅助函数。在自动生成用户名的案例中,一种方法是通过 Formik 的 setValues
。
onSubmit(values) {
// We added a `username` value for the user which is everything before @ in their email address.
setValues({
...values,
username: `@${values.email.split("@")[0]}`
});
}
输入电子邮件地址和密码,然后提交表单以查看您的新用户名!
总结
哇,我们在很短的时间内涵盖了很多内容。虽然这仅仅是关于满足表单所有需求以及 Formik 能够做的事情的冰山一角,但我希望这能为您提供一个新的工具,以便您下次在 React 应用程序中处理表单时可以使用它。
如果您准备好将 Formik 提升到一个新的水平,我建议您查看他们的资源作为起点。那里有很多好东西,它是 Formik 的功能以及更深入用例教程的良好档案。
祝您表单开发顺利!