告别泪水,使用 Formik 在 React 中处理表单(第一部分)
钩子重写 #1046
欢迎在推特上关注我,我很乐意接受您对话题或改进方面的建议。/克里斯
50% 的 React 开发者不使用表单库,而是自己构建表单,这是一个非常庞大的数字。随着人们逐渐了解 Formik,我认为这个数字会下降。
本文是系列文章之一:
- 不再流泪,使用 Formik 在 React 中处理表单(第一部分),我们来了!
- 不再流泪,使用 Formik 在 React 中处理表单,第二部分,正在努力
本文将涵盖以下内容:
- 表单概述,讨论表单的一般概念和不同的表单库。
- 本节将介绍如何使用 Formik 安装和设置 React 项目,以便您在本节结束后能够运行一个“Hello World”版本。
- 接下来,我们将创建一个相对贴近实际的表单示例,其中包含大多数类型的表单字段。
- 验证类型不止一种,例如每次字段值更改时进行验证,或者当您将焦点从一个字段切换到另一个字段时进行验证。让我们来看看如何在这两种模式之间切换。
本文是系列文章的一部分。Formik 包含太多有趣的主题,如果全部介绍,这篇文章会太长。因此,在下一篇文章中,我们将介绍使用 Yup 进行模式验证、异步验证,并探讨如何使用 Formik 的一些内置组件来简化代码:
资源
我已经为这两篇文章创建了代码仓库,所以如果你遇到问题,可以点击这里查看:表单演示仓库
一般表单和表单库
所以,表单,你最喜欢的话题?不是?嗯,我同意你的看法,我也不是很喜欢。这是一个非常重要的话题,有很多地方需要我们注意。以下列举一些(并非全部):
- 输入字段过多
- 输入字段太少
- 清除错误信息
- 不同类型的验证,例如电子邮件验证、数字验证、服务器端验证等。
- 它是如何进行验证的呢?比如每次字符更改时、输入字段更改时,或者当你按下“提交”按钮时?
这就是表单令人头疼的唯一原因吗?其实,这在某种程度上取决于你选择的单页应用(SPA)框架。我们选择的是 React.js 作为 SPA 框架。React 目前还没有官方的表单库,而且通常情况下,当框架开发者没有明确说明如何使用时,你最终会面临很多选择,例如:
- “自己动手”指的是构建你自己的表单处理方式。我们在本文中已经介绍过这方面的内容。
- 据其创始人称, Formsy 的目标是在灵活性和可重用性之间找到最佳平衡点。
- 本文介绍的库是Formik 。
- React Forms,这涉及到将表单值放入 Redux 状态中,至于这样做是好是坏,则取决于你的判断。
根据我最近在 Twitter 上进行的一项调查(我知道这不算非常科学,但还是值得一提),50% 的 React 开发者选择自己构建表单处理方式。这可是一个相当庞大的数字。我个人建议使用 Formik,因为它涵盖了我对表单库的大部分预期功能。请继续阅读,或许您也会认同 Formik 的确是一个非常强大的库。:)
如果您想了解上述库之间的更多区别,可以阅读这篇文章:https://codebrahma.com/form-libraries-in-react/
设置
和所有 React 项目一样,我们首先使用 Create React App (CRA) 工具。创建 React 应用非常简单,只需输入:
npx create-react-app [myapp]
cd [my app]
现在我们已经有了一个 React 应用,接下来让我们向其中添加 Formik 库:
yarn add formik
OR
npm install formik --save
让我们快速解释一下如何才能让 Formik 正常运行。我们需要执行以下操作:
- 导入 Formik 组件
- 定义
initialValues,这将给出该形式的初始值 validate这是一个以表单值作为输入参数的函数。该函数的目的是构建并返回一个表示表单状态的对象。该对象本身是键值对,其中键是表单字段的名称,值是检测到该字段错误时的错误消息。onSubmit这是一个我们需要定义的函数,用于确定按下提交键时应该发生什么。childFormik 组件的子组件用于定义表单及其包含字段的标记。如果存在任何表单错误,也会在此处渲染错误信息。
试驾一下
好的,那么我们来创建一个名为 FirstExample.js 的文件,我们将使用它来创建一个包含 Formik 的组件。首先,我们来导入相关代码:
// FirstExample.js
import { Formik } from 'formik';
接下来呢?我们需要一个组件来封装 Formik 组件,就像这样:
// FirstExample.js
import { Formik } from 'formik';
const FormikExample= () => (
<Formik>
// define markup
</Formik>
)
处理提交
这不会渲染任何内容,但我希望循序渐进,确保你不会跟不上。好的,下一步是添加一些标记,并调用 Formik 组件公开的 handleSubmit 方法,所以我们来修改你的代码:
// FirstExample.js
import React from 'react';
import { Formik } from 'formik';
const FirstExample = () => (
<Formik>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<input name="name" type="text" placeholder="Name"></input
<button>Submit</button>
</form>
)}
</Formik>
)
export default FirstExample;
此时在浏览器中运行此程序,您将收到以下错误:
是的,我们需要给 Formik 组件的 onSubmit 属性分配一个函数,接下来我们就来做这件事:
// FirstExample.js
import React from 'react';
import { Formik } from 'formik';
const FirstExample = () => (
<Formik onSubmit={values => {
console.log('submitting', values);
}} >
{({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<input name="name" type="text" placeholder="Name"></input>
<button>Submit</button>
</form>
)}
</Formik>
)
export default FirstExample;
现在让我们看看点击提交按钮后的输出结果:
没关系,接下来我们会通过讨论元素的生命周期来解释为什么会发生这种情况,希望能让大家更清楚地了解。
使用初始值处理元素输入生命周期
哎呀,我们哪里做错了?好的,我们需要告诉 Formik 组件如何处理表单中输入元素的生命周期。我们通过定义 `initialValues` 属性并为其提供一个包含表单内容的对象来实现这一点。我们还需要处理输入元素的 `onChange` 事件。请将代码更新为以下内容:
// FirstExample.js
import React from 'react';
import { Formik } from 'formik';
const FirstExample = () => (
<Formik
initialValues={{ name: '' }}
onSubmit={values => {
console.log('submitting', values);
}}>
{({ handleSubmit, handleChange, values }) => (
<form onSubmit={handleSubmit}>
<input onChange={handleChange}
value={values.name}
name="name"
type="text"
placeholder="Name">
</input>
<button>Submit</button>
</form>
)}
</Formik>
)
export default FirstExample;
所以我们做了以上三件事。
- 定义了初始值,并为其提供了一个表示表单输入值的对象。
- 将 handleChange 方法连接到输入元素的 onChange 事件
- 将输入元素的 value 属性连接
name到我们的 values 对象,特别是该属性。
现在,我们再按一次提交按钮,看看结果如何:
现在我们看到 Formik 可以正确识别我们的输入元素并处理其生命周期。哦耶 :)
验证
目前我们还没有设置任何验证,而这通常是我们处理表单时需要做的。那么,如何在 Formik 组件中实现验证呢?我们需要执行以下步骤:
- 在 Formik 组件中定义 validate 属性,它需要一个函数,该函数返回一个包含错误映射的对象。
- 从模板函数中的 errors 属性读取错误信息,并确保在设置该属性时显示错误。
好的,我们先从 validate 属性开始:
validate = {values => {
let errors = {};
if(!values.name) {
errors.name = 'Name is required';
}
return errors;
}}
如上所示,我们通过一个带有输入参数的函数来提供 validate 属性values。values 参数包含表单值,我们需要检查这些值以确定是否存在错误。如上所示,我们检查 name 元素并判断其是否为空。如果为空,则设置错误文本,最后返回 errors 对象。
下一步是确保我们的标记使用了我们刚刚构建的错误对象。这很容易做到,只需像这样添加即可:
{({
handleSubmit,
handleChange,
values,
errors
}) => (
<form onSubmit={handleSubmit}>
<div>
<input name="name"
onChange={handleChange}
name="name"
value={values.name}
type="text"
placeholder="Name">
</input>
{errors.name &&
<span style={{ color:"red", fontWeight: "bold" }}>
{errors.name}
</span>
}
</div>
<div>
<button>Submit</button>
</div>
</form>
)}
在浏览器中查看,现在看起来是这样的:
改进我们的形式
有很多方法可以改进我们使用 Formik 处理表单的方式,以下列举两种不同的方法:
- touched状态用于指示用户是否与此输入元素进行了交互。如果用户进行了交互,则 touched 的值为 true,例如,touched.name 的值为 true。
- 隐藏/禁用提交按钮。提交表单通常意味着您与后端服务器通信,后端服务器需要一些时间才能回复您。在此期间,最好确保用户无法点击提交按钮。
- 控制验证调用,通常验证函数会运行三次,你需要关注这三次:失去焦点时、更改时和提交时。
处理触碰
到目前为止,我们展示的表单示例都包含两种验证方式:一种是在 onChange 事件和 onBlur 事件上执行验证,除非您明确关闭验证,否则这是默认行为。然而,这样做会导致错误信息直接显示在字段旁边,即使您实际上还没有在该字段中输入任何字符。这显然不是好的用户体验。下面我用截图来说明这个问题:
上面我们在姓名栏输入了一个字符,然后又删除了该字符,因此触发了验证函数。不仅在我们仍在输入姓名栏时会触发验证,就连我们甚至还没操作过的地址栏也会显示验证错误。这可不太妙。那么我们该怎么办呢?我们可以确保除非输入了内容,否则这两个字段都不会显示任何验证错误。那么“输入了内容”是什么意思呢?它指的是我们在输入了内容后,就去操作其他字段了。下面我们用代码来演示一下:
// FormikTouched.js - excerpt showing the Formik components child function
{({
values,
errors,
touched ,
handleSubmit,
handleChange,
handleBlur
}) => (
<form onSubmit={handleSubmit}>
<h2>Form touched example</h2>
<div>
<input onBlur={handleBlur}
onChange={handleChange}
placeholder="name"
name="name"
value={values.name} />
{errors.name && touched.name &&
<div>{errors.name}</div>
}
</div>
<button>Submit</button>
</form>
)}
如上所示,我们将对 touched 属性的访问添加到子函数的输入参数中。我们还看到,我们在第一个输入参数中使用了 touched 的值touched.name。本质上,这意味着我们可以判断 touch.name 是否为真,从而显示错误信息。让我们放大来看:
<input onBlur={handleBlur}
onChange{handleChange}
placeholder="name"
name="name"
value={values.name} />
{errors.name && touched.name &&
<div>{errors.name}</div>
}
如上所示,我们需要添加逻辑&& touched.name来确保只有在实际与字段交互时才显示错误。
提交时隐藏/禁用提交按钮
我们都尝试过类似上述的方法。比如让用户耐心等待服务恢复,甚至显示加载指示器。但最终我们还是得出结论:在表单提交过程中,必须隐藏或至少禁用提交按钮。
Formik 提供了一个名为 `onSubmit` 的函数来帮助我们setSubmitting。让我们看看如何使用它,我们需要进入 `onSubmit` 的定义:
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
如您所见,我们使用 setTimeout 来模拟后端调用需要时间,在此期间我们不希望发生任何提交操作。我们是不是漏掉了什么,比如禁用提交按钮?是的,我们漏掉了。以下是禁用提交按钮的方法:
<button type="submit" disabled={isSubmitting} >
Submit
</button>
当我们点击提交按钮时,该属性被设置为 true。当我们在函数isSubmitting内部调用 setSubmitting(false) 时,该属性被设置为 false。onSubmitisSubmitting
控制验证调用
好的,所以我们已经确定,我们需要关注验证函数的三个调用点,分别是:
- 在 Blur 事件中,这意味着当我们将焦点从一个输入元素切换到下一个输入元素时,验证函数将会运行。
- 这意味着每次我们在输入元素中输入/删除字符时,验证函数都会运行。
- 此外,在提交表单时,验证函数也会运行。
控制失去焦点时的行为可以通过将属性值更改validateOnBlur为 false 来实现。其默认值为 true,这意味着每次元素失去焦点时都会运行验证函数。如果您知道验证操作开销很大,例如在验证函数中进行异步调用,那么最好尽可能减少验证的运行频率。我遇到的大多数表单都会在失去焦点时进行验证,因此除非验证操作开销非常大,或者您有充分的理由只在提交表单时运行验证,否则最好保持此功能启用。要控制此行为,您需要在标记中编写以下内容:
<Formik validateOnBlur={false}> // to shut it off
至于角色切换事件,每次切换角色时都会触发。通常来说,我觉得触发频率太高了,但你可能出于某种原因需要使用此功能。要控制其行为,请键入:
<Formik validateOnChange={false}> // to shut it off
概括
我们首先讨论了表单,包括不同的验证方法、何时进行验证、表单中应该包含多少信息等等。随后,我们提到了除 Formik 之外的其他表单库。之后,我们重点讲解了 Formik,包括如何安装和配置它,以及如何逐步构建表单。最后,我们探讨了改进表单的各种方法。
然而,这个库还有很多值得一提的功能,所以我们省略了某些部分,例如异步验证、使用 Yup 进行模式验证以及使用 Formiks 内置组件,从而为表单带来更轻松的体验。
这篇文章有点长,不过里面有一些GIF动图,希望你能耐心看到这里。下一篇文章我们将学习如何更高效地使用Formik事件,敬请期待。
文章来源:https://dev.to/azure/no-more-tears-handling-forms-in-react-using-formik-part-i-20kp









