¥Before we start
欢迎来到 Formik 教程。这将教你在 React 中构建简单和复杂表单所需的一切。
¥Welcome to the Formik tutorial. This will teach you everything you need to know to build simple and complex forms in React.
如果你不耐烦,只想在本地开始破解你的计算机,请查看 60 秒快速入门。
¥If you’re impatient and just want to start hacking on your machine locally, check out the 60-second quickstart.
¥What are we building?
在本教程中,我们将使用 React 和 Formik 构建一个复杂的时事通讯注册表单。
¥In this tutorial, we’ll build a complex newsletter signup form with React and Formik.
你可以在这里看到我们将要构建的内容:最后结果。如果代码对你来说没有意义,请不要担心!本教程的目的是帮助你了解 Formik。
¥You can see what we’ll be building here: Final Result. If the code doesn’t make sense to you, don’t worry! The goal of this tutorial is to help you understand Formik.
¥Prerequisites
你需要熟悉 HTML、CSS、现代 JavaScript 和 React(和 反应钩子)才能完全理解 Formik 及其工作原理。在本教程中,我们使用 箭头函数、let、const、扩展语法、destructuring、计算属性名称 和 async/await 。 你可以使用 Babel REPL 检查 ES6 代码编译成什么。
¥You’ll need to have familiarity with HTML, CSS, modern JavaScript, and React (and React Hooks) to fully understand Formik and how it works. In this tutorial, we’re using arrow functions, let, const, spread syntax, destructuring, computed property names, and async/await . You can use the Babel REPL to check what ES6 code compiles to.
¥Setup for the Tutorial
有两种方法可以完成本教程:你可以在浏览器中编写代码,也可以在计算机上设置本地开发环境。
¥There are two ways to complete this tutorial: you can either write the code in your browser, or you can set up a local development environment on your computer.
¥Setup Option 1: Write Code in the Browser
这是最快的入门方法!
¥This is the quickest way to get started!
首先,在新选项卡中打开此 起始代码。新选项卡应显示电子邮件地址输入、提交按钮和一些 React 代码。我们将在本教程中编辑 React 代码。
¥First, open this Starter Code in a new tab. The new tab should display an email address input, a submit button, and some React code. We’ll be editing the React code in this tutorial.
跳过第二个设置选项,并转到 概述 部分以获取 Formik 的概述。
¥Skip the second setup option, and go to the Overview section to get an overview of Formik.
¥Setup Option 2: Local Development Environment
这是完全可选的,对于本教程来说不是必需的!
¥This is completely optional and not required for this tutorial!
此设置需要更多工作,但允许你使用你选择的编辑器完成本教程。以下是要遵循的步骤:
¥This setup requires more work, but allows you to complete the tutorial using an editor of your choice. Here are the steps to follow:
确保你安装了最新版本的 Node.js。
¥Make sure you have a recent version of Node.js installed.
按照 Create React App 安装说明 做一个新项目。
¥Follow the installation instructions for Create React App to make a new project.
npx create-react-app my-app
安装 Formik
¥Install Formik
npm i formik
或者
¥Or
yarn add formik
删除新项目 src/
文件夹下的所有文件
¥Delete all files in the src/
folder of the new project
注意:
¥Note:
不要删除整个
src
文件夹,只删除其中的原始源文件。在下一步中,我们将使用该项目的示例替换默认源文件。¥Don’t delete the entire
src
folder, just the original source files inside it. We’ll replace the default source files with examples for this project in the next step.
cd my-appcd src# If you’re using a Mac or Linux:rm -f *# Or, if you’re on Windows:del *# Then, switch back to the project foldercd ..
在 src/
文件夹中添加一个名为 styles.css
的文件,其中包含 这个 CSS 代码。
¥Add a file named styles.css
in the src/
folder with this CSS code.
在 src/
文件夹中添加一个名为 index.js
的文件,其中包含 这段 JS 代码。
¥Add a file named index.js
in the src/
folder with this JS code.
现在在项目文件夹中运行 npm start
并在浏览器中打开 http://localhost:3000
。你应该看到一个电子邮件输入和一个提交按钮。
¥Now run npm start
in the project folder and open http://localhost:3000
in the browser. You should see an email input and a submit button.
我们建议按照 这些说明 为你的编辑器配置语法高亮。
¥We recommend following these instructions to configure syntax highlighting for your editor.
¥Help, I’m Stuck!
如果你遇到困难,请查看 Formik 的 GitHub 讨论。此外,Formium 社区 Discord 服务器 也是快速获得帮助的好方法。如果你没有收到答案,或者仍然遇到困难,请提出问题,我们将为你提供帮助。
¥If you get stuck, check out Formik’s GitHub Discussions. In addition, the Formium Community Discord Server is a great way to get help quickly too. If you don’t receive an answer, or if you remain stuck, please file an issue, and we’ll help you out.
¥Overview: What is Formik?
Formik 是一小群 React 组件和钩子,用于在 React 和 React Native 中构建表单。它有助于解决三个最烦人的部分:
¥Formik is a small group of React components and hooks for building forms in React and React Native. It helps with the three most annoying parts:
从表单状态获取值和从表单状态获取值
¥Getting values in and out of form state
验证和错误消息
¥Validation and error messages
处理表单提交
¥Handling form submission
通过将上述所有内容集中在一个地方,Formik 使事情井井有条 - 使测试、重构和推断表单变得轻而易举。
¥By colocating all of the above in one place, Formik keeps things organized--making testing, refactoring, and reasoning about your forms a breeze.
¥The Basics
我们将从使用 Formik 的最详细的方式开始。虽然这看起来有点啰嗦,但了解 Formik 如何构建自身非常重要,这样你就可以充分掌握可能性,并对其工作原理有一个完整的心智模型。
¥We’re going to start with the most verbose way of using Formik. While this may seem a bit long-winded, it’s important to see how Formik builds on itself so you have a full grasp of what’s possible and a complete mental model of how it works.
¥A simple newsletter signup form
想象一下我们想要为博客添加新闻通讯注册表单。首先,我们的表单只有一个名为 email
的字段。对于 Formik,这只是几行代码。
¥Imagine we want to add a newsletter signup form for a blog. To start, our form will have just one field named email
. With Formik, this is just a few lines of code.
import React from 'react';import { useFormik } from 'formik';const SignupForm = () => {// Pass the useFormik() hook initial form values and a submit function that will// be called when the form is submittedconst formik = useFormik({initialValues: {email: '',},onSubmit: values => {alert(JSON.stringify(values, null, 2));},});return (<form onSubmit={formik.handleSubmit}><label htmlFor="email">Email Address</label><inputid="email"name="email"type="email"onChange={formik.handleChange}value={formik.values.email}/><button type="submit">Submit</button></form>);};
我们将表单的 initialValues
和提交函数 (onSubmit
) 传递给 useFormik()
钩子。然后,该钩子会在我们称为 formik
的变量中返回一个包含表单状态和辅助方法的好东西。目前,我们关心的唯一辅助方法如下:
¥We pass our form’s initialValues
and a submission function (onSubmit
) to the useFormik()
hook. The hook then returns to us a goodie bag of form state and helper methods in a variable we call formik
. For now, the only helper methods we care about are as follows:
handleSubmit
:提交处理程序
¥handleSubmit
: A submission handler
handleChange
:传递给每个 <input>
、<select>
或 <textarea>
的更改处理程序
¥handleChange
: A change handler to pass to each <input>
, <select>
, or <textarea>
values
:我们表单的当前值
¥values
: Our form’s current values
正如你在上面看到的,我们将每个参数传递给各自的 props...就是这样!我们现在可以拥有一个由 Formik 提供支持的工作表单。我们可以只使用 useFormik()
,而不是自己管理表单的值并为每个输入编写自己的自定义事件处理程序。
¥As you can see above, we pass each of these to their respective props...and that’s it! We can now have a working form powered by Formik. Instead of managing our form’s values on our own and writing our own custom event handlers for every single input, we can just use useFormik()
.
这非常简洁,但只有一个输入,使用 useFormik()
的好处尚不清楚。因此,让我们再添加两个输入:其中一个代表用户的名字和姓氏,我们将在表单中将其存储为 firstName
和 lastName
。
¥This is pretty neat, but with just one single input, the benefits of using useFormik()
are unclear. So let’s add two more inputs: one for the user’s first and last name, which we’ll store as firstName
and lastName
in the form.
import React from 'react';import { useFormik } from 'formik';const SignupForm = () => {// Note that we have to initialize ALL of fields with values. These// could come from props, but since we don’t want to prefill this form,// we just use an empty string. If we don’t do this, React will yell// at us.const formik = useFormik({initialValues: {firstName: '',lastName: '',email: '',},onSubmit: values => {alert(JSON.stringify(values, null, 2));},});return (<form onSubmit={formik.handleSubmit}><label htmlFor="firstName">First Name</label><inputid="firstName"name="firstName"type="text"onChange={formik.handleChange}value={formik.values.firstName}/><label htmlFor="lastName">Last Name</label><inputid="lastName"name="lastName"type="text"onChange={formik.handleChange}value={formik.values.lastName}/><label htmlFor="email">Email Address</label><inputid="email"name="email"type="email"onChange={formik.handleChange}value={formik.values.email}/><button type="submit">Submit</button></form>);};
如果你仔细查看我们的新代码,你会注意到一些模式和对称性形成。
¥If you look carefully at our new code, you’ll notice some patterns and symmetry forming.
我们为每个 HTML 输入重用相同的更改处理函数 handleChange
¥We reuse the same exact change handler function handleChange
for each HTML input
我们传递了与我们在 initialValues
中定义的属性相匹配的 id
和 name
HTML 属性
¥We pass an id
and name
HTML attribute that matches the property we defined in initialValues
我们使用相同的名称访问字段的值 (email
-> formik.values.email
)
¥We access the field’s value using the same name (email
-> formik.values.email
)
如果你熟悉使用普通 React 构建表单,你可以将 Formik 的 handleChange
视为如下工作方式:
¥If you’re familiar with building forms with plain React, you can think of Formik’s handleChange
as working like this:
const [values, setValues] = React.useState({});const handleChange = event => {setValues(prevValues => ({...prevValues,// we use the name to tell Formik which key of `values` to update[event.target.name]: event.target.value});}
¥Validation
虽然我们的联系表有效,但功能还不够完善;用户可以提交它,但它不会告诉他们哪些字段(如果有)是必填的。
¥While our contact form works, it’s not quite feature-complete; users can submit it, but it doesn’t tell them which (if any) fields are required.
如果我们可以使用浏览器的内置 HTML 输入验证,我们可以为每个输入添加 required
属性,指定最小/最大长度(maxlength
和 minlength
),和/或添加 pattern
属性用于正则表达式验证 这些输入中的每一个。如果我们能摆脱它们,那就太好了。然而,HTML 验证有其局限性。首先,它只能在浏览器中运行!所以这对于 React Native 来说显然是不可行的。其次,很难/不可能向我们的用户显示自定义错误消息。第三,它非常简陋。
¥If we’re okay with using the browser’s built-in HTML input validation, we could add a required
prop to each of our inputs, specify minimum/maximum lengths (maxlength
and minlength
), and/or add a pattern
prop for regex validation for each of these inputs. These are great if we can get away with them. However, HTML validation has its limitations. First, it only works in the browser! So this clearly is not viable for React Native. Second, it’s hard/impossible to show custom error messages to our user. Third, it’s very janky.
如前所述,Formik 不仅跟踪表单的 values
,还跟踪其验证和错误消息。要使用 JS 添加验证,让我们指定一个自定义验证函数并将其作为 validate
传递给 useFormik()
钩子。如果存在错误,这个自定义验证函数应该生成一个 error
对象,其形状与我们的 values
/initialValues
匹配。再次...对称...是的...
¥As mentioned earlier, Formik keeps track of not only your form’s values
, but also its validation and error messages. To add validation with JS, let’s specify a custom validation function and pass it as validate
to the useFormik()
hook. If an error exists, this custom validation function should produce an error
object with a matching shape to our values
/initialValues
. Again...symmetry...yes...
import React from 'react';import { useFormik } from 'formik';// A custom validation function. This must return an object// which keys are symmetrical to our values/initialValuesconst validate = values => {const errors = {};if (!values.firstName) {errors.firstName = 'Required';} else if (values.firstName.length > 15) {errors.firstName = 'Must be 15 characters or less';}if (!values.lastName) {errors.lastName = 'Required';} else if (values.lastName.length > 20) {errors.lastName = 'Must be 20 characters or less';}if (!values.email) {errors.email = 'Required';} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {errors.email = 'Invalid email address';}return errors;};const SignupForm = () => {// Pass the useFormik() hook initial form values, a validate function that will be called when// form values change or fields are blurred, and a submit function that will// be called when the form is submittedconst formik = useFormik({initialValues: {firstName: '',lastName: '',email: '',},validate,onSubmit: values => {alert(JSON.stringify(values, null, 2));},});return (<form onSubmit={formik.handleSubmit}><label htmlFor="firstName">First Name</label><inputid="firstName"name="firstName"type="text"onChange={formik.handleChange}value={formik.values.firstName}/>{formik.errors.firstName ? <div>{formik.errors.firstName}</div> : null}<label htmlFor="lastName">Last Name</label><inputid="lastName"name="lastName"type="text"onChange={formik.handleChange}value={formik.values.lastName}/>{formik.errors.lastName ? <div>{formik.errors.lastName}</div> : null}<label htmlFor="email">Email Address</label><inputid="email"name="email"type="email"onChange={formik.handleChange}value={formik.values.email}/>{formik.errors.email ? <div>{formik.errors.email}</div> : null}<button type="submit">Submit</button></form>);};
formik.errors
通过自定义验证函数填充。默认情况下,Formik 将在每次击键(更改事件)后、每个输入的 模糊事件 以及提交之前进行验证。仅当没有错误时(即,如果我们的 validate
函数返回 {}
),我们传递给 useFormik()
的 onSubmit
函数才会执行。
¥formik.errors
is populated via the custom validation function. By default, Formik will validate after each keystroke (change event), each input’s blur event, as well as prior to submission. The onSubmit
function we passed to useFormik()
will be executed only if there are no errors (i.e. if our validate
function returns {}
).
¥Visited fields
虽然我们的表单有效,并且我们的用户会看到每个错误,但这对他们来说并不是一个很好的用户体验。由于我们的验证函数在每次击键时针对整个表单的 values
运行,因此我们的 errors
对象包含任何给定时刻的所有验证错误。在我们的组件中,我们只是检查是否存在错误,然后立即将其显示给用户。这很尴尬,因为我们将显示用户尚未访问过的字段的错误消息。大多数时候,我们只想在用户在该字段中输入完毕后显示该字段的错误消息。
¥While our form works, and our users see each error, it’s not a great user experience for them. Since our validation function runs on each keystroke against the entire form’s values
, our errors
object contains all validation errors at any given moment. In our component, we’re just checking if an error exists and then immediately showing it to the user. This is awkward since we’re going to show error messages for fields that the user hasn’t even visited yet. Most of the time, we only want to show a field’s error message after our user is done typing in that field.
与 errors
和 values
一样,Formik 会跟踪已访问过的字段。它将这些信息存储在一个名为 touched
的对象中,该对象也反映了 values
/initialValues
的形状。touched
的键是字段名称,touched
的值是布尔值 true
/false
。
¥Like errors
and values
, Formik keeps track of which fields have been visited. It stores this information in an object called touched
that also mirrors the shape of values
/initialValues
. The keys of touched
are the field names, and the values of touched
are booleans true
/false
.
为了利用 touched
,我们将 formik.handleBlur
传递给每个输入的 onBlur
属性。此函数的工作方式与 formik.handleChange
类似,因为它使用 name
属性来确定要更新哪个字段。
¥To take advantage of touched
, we pass formik.handleBlur
to each input’s onBlur
prop. This function works similarly to formik.handleChange
in that it uses the name
attribute to figure out which field to update.
import React from 'react';import { useFormik } from 'formik';const validate = values => {const errors = {};if (!values.firstName) {errors.firstName = 'Required';} else if (values.firstName.length > 15) {errors.firstName = 'Must be 15 characters or less';}if (!values.lastName) {errors.lastName = 'Required';} else if (values.lastName.length > 20) {errors.lastName = 'Must be 20 characters or less';}if (!values.email) {errors.email = 'Required';} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {errors.email = 'Invalid email address';}return errors;};const SignupForm = () => {const formik = useFormik({initialValues: {firstName: '',lastName: '',email: '',},validate,onSubmit: values => {alert(JSON.stringify(values, null, 2));},});return (<form onSubmit={formik.handleSubmit}><label htmlFor="firstName">First Name</label><inputid="firstName"name="firstName"type="text"onChange={formik.handleChange}onBlur={formik.handleBlur}value={formik.values.firstName}/>{formik.errors.firstName ? <div>{formik.errors.firstName}</div> : null}<label htmlFor="lastName">Last Name</label><inputid="lastName"name="lastName"type="text"onChange={formik.handleChange}onBlur={formik.handleBlur}value={formik.values.lastName}/>{formik.errors.lastName ? <div>{formik.errors.lastName}</div> : null}<label htmlFor="email">Email Address</label><inputid="email"name="email"type="email"onChange={formik.handleChange}onBlur={formik.handleBlur}value={formik.values.email}/>{formik.errors.email ? <div>{formik.errors.email}</div> : null}<button type="submit">Submit</button></form>);};
差不多了!现在我们正在跟踪 touched
,我们现在可以更改错误消息渲染逻辑,以仅显示给定字段的错误消息(如果存在)并且用户已访问该字段。
¥Almost there! Now that we’re tracking touched
, we can now change our error message render logic to only show a given field’s error message if it exists and if our user has visited that field.
import React from 'react';import { useFormik } from 'formik';const validate = values => {const errors = {};if (!values.firstName) {errors.firstName = 'Required';} else if (values.firstName.length > 15) {errors.firstName = 'Must be 15 characters or less';}if (!values.lastName) {errors.lastName = 'Required';} else if (values.lastName.length > 20) {errors.lastName = 'Must be 20 characters or less';}if (!values.email) {errors.email = 'Required';} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {errors.email = 'Invalid email address';}return errors;};const SignupForm = () => {const formik = useFormik({initialValues: {firstName: '',lastName: '',email: '',},validate,onSubmit: values => {alert(JSON.stringify(values, null, 2));},});return (<form onSubmit={formik.handleSubmit}><label htmlFor="firstName">First Name</label><inputid="firstName"name="firstName"type="text"onChange={formik.handleChange}onBlur={formik.handleBlur}value={formik.values.firstName}/>{formik.touched.firstName && formik.errors.firstName ? (<div>{formik.errors.firstName}</div>) : null}<label htmlFor="lastName">Last Name</label><inputid="lastName"name="lastName"type="text"onChange={formik.handleChange}onBlur={formik.handleBlur}value={formik.values.lastName}/>{formik.touched.lastName && formik.errors.lastName ? (<div>{formik.errors.lastName}</div>) : null}<label htmlFor="email">Email Address</label><inputid="email"name="email"type="email"onChange={formik.handleChange}onBlur={formik.handleBlur}value={formik.values.email}/>{formik.touched.email && formik.errors.email ? (<div>{formik.errors.email}</div>) : null}<button type="submit">Submit</button></form>);};
¥Schema Validation with Yup
正如你在上面看到的,验证由你决定。你可以随意编写自己的验证器或使用第三方辅助程序库。Formik 的作者/大部分用户使用 贾森·昆斯 的库 是的 进行对象模式验证。是的,它有一个类似于 Joi 和 React PropTypes 的 API,但对于浏览器来说也足够小,对于运行时使用来说也足够快。你可以在这里尝试一下这款 REPL。
¥As you can see above, validation is left up to you. Feel free to write your own validators or use a 3rd-party helper library. Formik’s authors/a large portion of its users use Jason Quense’s library Yup for object schema validation. Yup has an API that’s similar to Joi and React PropTypes, but is also small enough for the browser and fast enough for runtime usage. You can try it out here with this REPL.
由于 Formik 作者/用户非常喜欢 Yup,Formik 有一个名为 validationSchema
的 Yup 特殊配置属性,它会自动将 Yup 的验证错误消息转换为一个漂亮的对象,其密钥与 values
/initialValues
/touched
匹配(就像任何自定义验证函数必须 )。不管怎样,你可以像这样从 NPM/yarn 安装 Yup...
¥Since Formik authors/users love Yup so much, Formik has a special configuration prop for Yup called validationSchema
which will automatically transform Yup’s validation errors messages into a pretty object whose keys match values
/initialValues
/touched
(just like any custom validation function would have to). Anyways, you can install Yup from NPM/yarn like so...
npm install yup --save# or via yarnyarn add yup
要了解 Yup 的工作原理,让我们摆脱自定义验证函数 validate
并使用 Yup 和 validationSchema
重新编写验证:
¥To see how Yup works, let’s get rid of our custom validation function validate
and re-write our validation with Yup and validationSchema
:
import React from 'react';import { useFormik } from 'formik';import * as Yup from 'yup';const SignupForm = () => {const formik = useFormik({initialValues: {firstName: '',lastName: '',email: '',},validationSchema: Yup.object({firstName: Yup.string().max(15, 'Must be 15 characters or less').required('Required'),lastName: Yup.string().max(20, 'Must be 20 characters or less').required('Required'),email: Yup.string().email('Invalid email address').required('Required'),}),onSubmit: values => {alert(JSON.stringify(values, null, 2));},});return (<form onSubmit={formik.handleSubmit}><label htmlFor="firstName">First Name</label><inputid="firstName"name="firstName"type="text"onChange={formik.handleChange}onBlur={formik.handleBlur}value={formik.values.firstName}/>{formik.touched.firstName && formik.errors.firstName ? (<div>{formik.errors.firstName}</div>) : null}<label htmlFor="lastName">Last Name</label><inputid="lastName"name="lastName"type="text"onChange={formik.handleChange}onBlur={formik.handleBlur}value={formik.values.lastName}/>{formik.touched.lastName && formik.errors.lastName ? (<div>{formik.errors.lastName}</div>) : null}<label htmlFor="email">Email Address</label><inputid="email"name="email"type="email"onChange={formik.handleChange}onBlur={formik.handleBlur}value={formik.values.email}/>{formik.touched.email && formik.errors.email ? (<div>{formik.errors.email}</div>) : null}<button type="submit">Submit</button></form>);};
再说一次,是的,是 100% 可选的。不过,我们建议你尝试一下。正如你在上面所看到的,我们仅用 10 行代码(而不是 30 行)表达了完全相同的验证函数。Formik 的核心设计原则之一是帮助你保持井井有条。是的,这确实有很大帮助 - 模式非常具有表现力、直观(因为它们反映了你的值)并且可重用。无论你是否使用 Yup,我们都强烈建议你在应用中共享常用的验证方法。这将确保公共字段(例如电子邮件、街道地址、用户名、调用号码等)得到一致验证,并带来更好的用户体验。
¥Again, Yup is 100% optional. However, we suggest giving it a try. As you can see above, we expressed the exact same validation function with just 10 lines of code instead of 30. One of Formik’s core design principles is to help you stay organized. Yup definitely helps a lot with this--schemas are extremely expressive, intuitive (since they mirror your values), and reusable. Whether or not you use Yup, we highly recommended you share commonly used validation methods across your application. This will ensure that common fields (e.g. email, street addresses, usernames, phone numbers, etc.) are validated consistently and result in a better user experience.
¥Reducing Boilerplate
getFieldProps()
上面的代码非常明确地说明了 Formik 正在做什么。onChange
-> handleChange
、onBlur
-> handleBlur
,依此类推。但是,为了节省你的时间,useFormik()
返回一个名为 formik.getFieldProps()
的辅助方法,以加快连接输入的速度。给定一些字段级信息,它会返回给定字段的确切的 onChange
、onBlur
、value
、checked
组。然后你可以将其传播到 input
、select
或 textarea
上。
¥The code above is very explicit about exactly what Formik is doing. onChange
-> handleChange
, onBlur
-> handleBlur
, and so on. However, to save you time, useFormik()
returns a helper method called formik.getFieldProps()
to make it faster to wire up inputs. Given some field-level info, it returns to you the exact group of onChange
, onBlur
, value
, checked
for a given field. You can then spread that on an input
, select
, or textarea
.
import React from 'react';import { useFormik } from 'formik';import * as Yup from 'yup';const SignupForm = () => {const formik = useFormik({initialValues: {firstName: '',lastName: '',email: '',},validationSchema: Yup.object({firstName: Yup.string().max(15, 'Must be 15 characters or less').required('Required'),lastName: Yup.string().max(20, 'Must be 20 characters or less').required('Required'),email: Yup.string().email('Invalid email address').required('Required'),}),onSubmit: values => {alert(JSON.stringify(values, null, 2));},});return (<form onSubmit={formik.handleSubmit}><label htmlFor="firstName">First Name</label><inputid="firstName"type="text"{...formik.getFieldProps('firstName')}/>{formik.touched.firstName && formik.errors.firstName ? (<div>{formik.errors.firstName}</div>) : null}<label htmlFor="lastName">Last Name</label><input id="lastName" type="text" {...formik.getFieldProps('lastName')} />{formik.touched.lastName && formik.errors.lastName ? (<div>{formik.errors.lastName}</div>) : null}<label htmlFor="email">Email Address</label><input id="email" type="email" {...formik.getFieldProps('email')} />{formik.touched.email && formik.errors.email ? (<div>{formik.errors.email}</div>) : null}<button type="submit">Submit</button></form>);};
¥Leveraging React Context
我们上面的代码再次非常明确地说明了 Formik 正在做什么。onChange
-> handleChange
、onBlur
-> handleBlur
,依此类推。然而,我们仍然必须手动将每个输入传递给这个 "属性获取器" getFieldProps()
。为了节省你更多时间,Formik 配备了 反应上下文 支持的 API/组件,让生活更轻松,代码更简洁:<Formik />
、<Form />
、<Field />
和 <ErrorMessage />
。更明确地说,它们隐式地使用 React Context 来与父 <Formik />
状态/方法连接。
¥Our code above is again very explicit about exactly what Formik is doing. onChange
-> handleChange
, onBlur
-> handleBlur
, and so on. However, we still have to manually pass each input this "prop getter" getFieldProps()
. To save you even more time, Formik comes with React Context-powered API/components to make life easier and code less verbose: <Formik />
, <Form />
, <Field />
, and <ErrorMessage />
. More explicitly, they use React Context implicitly to connect with the parent <Formik />
state/methods.
由于这些组件使用 React Context,我们需要渲染一个 反应上下文提供者 来保存我们的表单状态和树中的助手。如果你自己这样做,它看起来会像:
¥Since these components use React Context, we need to render a React Context Provider that holds our form state and helpers in our tree. If you did this yourself, it would look like:
import React from 'react';import { useFormik } from 'formik';// Create empty contextconst FormikContext = React.createContext({});// Place all of what’s returned by useFormik into contextexport const Formik = ({ children, ...props }) => {const formikStateAndHelpers = useFormik(props);return (<FormikContext.Provider value={formikStateAndHelpers}>{typeof children === 'function'? children(formikStateAndHelpers): children}</FormikContext.Provider>);};
幸运的是,我们已经在 <Formik>
组件中为你完成了此操作,其工作原理如下。
¥Luckily, we’ve done this for you in a <Formik>
component that works just like this.
现在让我们将 useFormik()
钩子替换为 Formik 的 <Formik>
组件/渲染属性。由于它是一个组件,我们将把传递给 useFormik()
的对象转换为 JSX,每个键都成为一个 prop。
¥Let’s now swap out the useFormik()
hook for Formik’s <Formik>
component/render-prop. Since it’s a component, we’ll convert the object passed to useFormik()
to JSX, with each key becoming a prop.
import React from 'react';import { Formik } from 'formik';import * as Yup from 'yup';const SignupForm = () => {return (<FormikinitialValues={{ firstName: '', lastName: '', email: '' }}validationSchema={Yup.object({firstName: Yup.string().max(15, 'Must be 15 characters or less').required('Required'),lastName: Yup.string().max(20, 'Must be 20 characters or less').required('Required'),email: Yup.string().email('Invalid email address').required('Required'),})}onSubmit={(values, { setSubmitting }) => {setTimeout(() => {alert(JSON.stringify(values, null, 2));setSubmitting(false);}, 400);}}>{formik => (<form onSubmit={formik.handleSubmit}><label htmlFor="firstName">First Name</label><inputid="firstName"type="text"{...formik.getFieldProps('firstName')}/>{formik.touched.firstName && formik.errors.firstName ? (<div>{formik.errors.firstName}</div>) : null}<label htmlFor="lastName">Last Name</label><inputid="lastName"type="text"{...formik.getFieldProps('lastName')}/>{formik.touched.lastName && formik.errors.lastName ? (<div>{formik.errors.lastName}</div>) : null}<label htmlFor="email">Email Address</label><input id="email" type="email" {...formik.getFieldProps('email')} />{formik.touched.email && formik.errors.email ? (<div>{formik.errors.email}</div>) : null}<button type="submit">Submit</button></form>)}</Formik>);};
正如你在上面看到的,我们替换了 useFormik()
钩子并用 <Formik>
组件替换它。<Formik>
组件接受一个函数作为其子组件(也称为 渲染属性)。它的参数与 useFormik()
返回的对象完全相同(事实上,<Formik>
在内部调用了 useFormik()
!)。因此,我们的表单与以前一样工作,只是现在我们可以使用新的组件以更简洁的方式表达自己。
¥As you can see above, we swapped out useFormik()
hook and replaced it with the <Formik>
component. The <Formik>
component accepts a function as its children (a.k.a. a render prop). Its argument is the exact same object returned by useFormik()
(in fact, <Formik>
calls useFormik()
internally!). Thus, our form works the same as before, except now we can use new components to express ourselves in a more concise manner.
import React from 'react';import { Formik, Field, Form, ErrorMessage } from 'formik';import * as Yup from 'yup';const SignupForm = () => {return (<FormikinitialValues={{ firstName: '', lastName: '', email: '' }}validationSchema={Yup.object({firstName: Yup.string().max(15, 'Must be 15 characters or less').required('Required'),lastName: Yup.string().max(20, 'Must be 20 characters or less').required('Required'),email: Yup.string().email('Invalid email address').required('Required'),})}onSubmit={(values, { setSubmitting }) => {setTimeout(() => {alert(JSON.stringify(values, null, 2));setSubmitting(false);}, 400);}}><Form><label htmlFor="firstName">First Name</label><Field name="firstName" type="text" /><ErrorMessage name="firstName" /><label htmlFor="lastName">Last Name</label><Field name="lastName" type="text" /><ErrorMessage name="lastName" /><label htmlFor="email">Email Address</label><Field name="email" type="email" /><ErrorMessage name="email" /><button type="submit">Submit</button></Form></Formik>);};
默认情况下,<Field>
组件将渲染 <input>
组件,给定 name
属性,该组件将隐式获取相应的 onChange
、onBlur
、value
属性,并将它们传递给元素以及传递给它的任何属性。然而,由于并非所有内容都是输入,<Field>
还接受一些其他属性,让你渲染任何你想要的内容。一些例子..
¥The <Field>
component by default will render an <input>
component that, given a name
prop, will implicitly grab the respective onChange
, onBlur
, value
props and pass them to the element as well as any props you pass to it. However, since not everything is an input, <Field>
also accepts a few other props to let you render whatever you want. Some examples..
// <input className="form-input" placeHolder="Jane" /><Field name="firstName" className="form-input" placeholder="Jane" />// <textarea className="form-textarea"/></textarea><Field name="message" as="textarea" className="form-textarea" />// <select className="my-select"/><Field name="colors" as="select" className="my-select"><option value="red">Red</option><option value="green">Green</option><option value="blue">Blue</option></Field>
React 是关于组合的,虽然我们已经减少了很多 prop-drilling,但我们仍然为每个输入重复使用 label
、<Field>
和 <ErrorMessage>
。我们可以通过抽象做得更好!使用 Formik,你可以而且应该构建可在应用中共享的可重用输入基础类型组件。结果我们的 <Field>
render-prop 组件有一个姐妹,她的名字是 useField
,它将做同样的事情,但是通过 React Hooks!看一下这个...
¥React is all about composition, and while we’ve cut down on a lot of the prop-drilling, we’re still repeating ourselves with a label
, <Field>
, and <ErrorMessage>
for each of our inputs. We can do better with an abstraction! With Formik, you can and should build reusable input primitive components that you can share around your application. Turns out our <Field>
render-prop component has a sister and her name is useField
that’s going to do the same thing, but via React Hooks! Check this out...
import React from 'react';import ReactDOM from 'react-dom';import { Formik, Form, useField } from 'formik';import * as Yup from 'yup';const MyTextInput = ({ label, ...props }) => {// useField() returns [formik.getFieldProps(), formik.getFieldMeta()]// which we can spread on <input>. We can use field meta to show an error// message if the field is invalid and it has been touched (i.e. visited)const [field, meta] = useField(props);return (<><label htmlFor={props.id || props.name}>{label}</label><input className="text-input" {...field} {...props} />{meta.touched && meta.error ? (<div className="error">{meta.error}</div>) : null}</>);};const MyCheckbox = ({ children, ...props }) => {// React treats radios and checkbox inputs differently from other input types: select and textarea.// Formik does this too! When you specify `type` to useField(), it will// return the correct bag of props for you -- a `checked` prop will be included// in `field` alongside `name`, `value`, `onChange`, and `onBlur`const [field, meta] = useField({ ...props, type: 'checkbox' });return (<div><label className="checkbox-input"><input type="checkbox" {...field} {...props} />{children}</label>{meta.touched && meta.error ? (<div className="error">{meta.error}</div>) : null}</div>);};const MySelect = ({ label, ...props }) => {const [field, meta] = useField(props);return (<div><label htmlFor={props.id || props.name}>{label}</label><select {...field} {...props} />{meta.touched && meta.error ? (<div className="error">{meta.error}</div>) : null}</div>);};// And now we can use theseconst SignupForm = () => {return (<><h1>Subscribe!</h1><FormikinitialValues={{firstName: '',lastName: '',email: '',acceptedTerms: false, // added for our checkboxjobType: '', // added for our select}}validationSchema={Yup.object({firstName: Yup.string().max(15, 'Must be 15 characters or less').required('Required'),lastName: Yup.string().max(20, 'Must be 20 characters or less').required('Required'),email: Yup.string().email('Invalid email address').required('Required'),acceptedTerms: Yup.boolean().required('Required').oneOf([true], 'You must accept the terms and conditions.'),jobType: Yup.string().oneOf(['designer', 'development', 'product', 'other'],'Invalid Job Type').required('Required'),})}onSubmit={(values, { setSubmitting }) => {setTimeout(() => {alert(JSON.stringify(values, null, 2));setSubmitting(false);}, 400);}}><Form><MyTextInputlabel="First Name"name="firstName"type="text"placeholder="Jane"/><MyTextInputlabel="Last Name"name="lastName"type="text"placeholder="Doe"/><MyTextInputlabel="Email Address"name="email"type="email"placeholder="jane@formik.com"/><MySelect label="Job Type" name="jobType"><option value="">Select a job type</option><option value="designer">Designer</option><option value="development">Developer</option><option value="product">Product Manager</option><option value="other">Other</option></MySelect><MyCheckbox name="acceptedTerms">I accept the terms and conditions</MyCheckbox><button type="submit">Submit</button></Form></Formik></>);};
正如你在上面看到的,useField()
使我们能够将 React 组件的任何类型的输入连接到 Formik,就像它是 <Field>
+ <ErrorMessage>
一样。我们可以使用它来构建一组满足我们需求的可重用输入。
¥As you can see above, useField()
gives us the ability to connect any kind input of React component to Formik as if it were a <Field>
+ <ErrorMessage>
. We can use it to build a group of reusable inputs that fit our needs.
¥Wrapping Up
恭喜!你已使用 Formik 创建了一个注册表单:
¥Congratulations! You’ve created a signup form with Formik that:
具有复杂的验证逻辑和丰富的错误消息
¥Has complex validation logic and rich error messages
在正确的时间向用户正确显示错误消息(在模糊字段之后)
¥Properly displays errors messages to the user at the correct time (after they have blurred a field)
利用你自己的自定义输入组件,你可以在应用中的其他表单上使用
¥Leverages your own custom input components you can use on other forms in your app
干得好!我们希望你现在感觉自己已经很好地掌握了 Formik 的工作原理。
¥Nice work! We hope you now feel like you have a decent grasp on how Formik works.
在这里查看最终结果:最后结果。
¥Check out the final result here: Final Result.
如果你有额外的时间或想要练习新的 Formik 技能,这里有一些你可以对注册表单进行改进的想法,这些想法按难度递增的顺序列出:
¥If you have extra time or want to practice your new Formik skills, here are some ideas for improvements that you could make to the signup form which are listed in order of increasing difficulty:
当用户尝试提交时禁用提交按钮(提示:formik.isSubmitting
)
¥Disable the submit button while the user has attempted to submit (hint: formik.isSubmitting
)
添加带有 formik.handleReset
或 <button type="reset">
的重置按钮。
¥Add a reset button with formik.handleReset
or <button type="reset">
.
根据传递给 <SignupForm>
的 URL 查询字符串或属性预填充 initialValues
。
¥Pre-populate initialValues
based on URL query string or props passed to <SignupForm>
.
当字段出现错误且未聚焦时,将输入边框颜色更改为红色
¥Change the input border color to red when a field has an error and isn’t focused
为每个字段添加显示错误且已访问时的摇动动画
¥Add a shake animation to each field when it displays an error and has been visited
将表单状态保留到浏览器的 sessionStorage,以便在页面刷新之间保持表单进度
¥Persist form state to the browser’s sessionStorage so that form progress is kept in between page refreshes
在本教程中,我们接触了 Formik 概念,包括表单状态、字段、验证、钩子、渲染属性和 React 上下文。有关每个主题的更详细说明,请查看 documentation 的其余部分。要了解有关在教程中定义组件和钩子的更多信息,请查看 API 参考。
¥Throughout this tutorial, we touched on Formik concepts including form state, fields, validation, hooks, render props, and React context. For a more detailed explanation of each of these topics, check out the rest of the documentation. To learn more about defining the components and hooks in the tutorial, check out the API reference.