教程

开始之前

欢迎来到 Formik 教程。本教程将教你构建 React 中简单和复杂表单所需的一切。

如果你很着急,只想在本地机器上开始编写代码,请查看60 秒快速入门

我们正在构建什么?

在本教程中,我们将使用 React 和 Formik 构建一个复杂的新闻通讯注册表单。

您可以在此处查看我们将要构建的内容:最终结果。如果您不理解代码,请不要担心!本教程的目的是帮助您理解 Formik。

先决条件

您需要熟悉 HTML、CSS、现代 JavaScriptReact(以及 React Hooks)才能完全理解 Formik 及其工作原理。在本教程中,我们使用的是 箭头函数letconst扩展语法解构计算属性名async/await。您可以使用 Babel REPL 检查 ES6 代码编译成的结果。

教程设置

完成本教程有两种方法:您可以在浏览器中编写代码,或者在您的计算机上设置本地开发环境。

设置选项 1:在浏览器中编写代码

这是最快的入门方式!

首先,在新标签页中打开此启动代码。新标签页应显示一个电子邮件地址输入框、一个提交按钮和一些 React 代码。在本教程中,我们将编辑 React 代码。

跳过第二个设置选项,并转到概述部分以获取 Formik 的概述。

设置选项 2:本地开发环境

这完全是可选的,在本教程中不需要!

可选:使用您喜欢的文本编辑器在本地学习的说明

此设置需要更多工作,但允许您使用您选择的编辑器完成本教程。以下是需要遵循的步骤

  1. 确保您已安装最新版本的Node.js
  2. 按照Create React App 的安装说明创建一个新项目。
npx create-react-app my-app
  1. 安装 Formik
npm i formik

yarn add formik
  1. 删除新项目中 src/ 文件夹中的所有文件

注意

不要删除整个 src 文件夹,只需删除其中的原始源文件即可。我们将在下一步中用此项目的示例替换默认的源文件。

cd my-app
cd src
# If you’re using a Mac or Linux:
rm -f *
# Or, if you’re on Windows:
del *
# Then, switch back to the project folder
cd ..
  1. src/ 文件夹中添加一个名为 styles.css 的文件,其中包含此 CSS 代码

  2. src/ 文件夹中添加一个名为 index.js 的文件,其中包含此 JS 代码

现在在项目文件夹中运行 npm start,并在浏览器中打开 http://localhost:3000。您应该会看到一个电子邮件输入框和一个提交按钮。

我们建议按照这些说明配置编辑器的语法高亮显示。

我卡住了,怎么办?

如果您遇到问题,请查看 Formik 的GitHub 讨论。此外,Formium 社区 Discord 服务器也是一个快速获得帮助的好方法。如果您没有收到回复,或者仍然卡住,请提交问题,我们将帮助您解决。

概述:什么是 Formik?

Formik 是一小组 React 组件和 Hook,用于在 React 和 React Native 中构建表单。它可以帮助解决三个最令人烦恼的部分

  1. 在表单状态中获取和设置值
  2. 验证和错误消息
  3. 处理表单提交

通过将以上所有内容集中在一个地方,Formik 使事情井然有序,从而使测试、重构和推演表单变得轻而易举。

基础知识

我们将从使用 Formik 的最冗长的方式开始。虽然这看起来可能有点啰嗦,但了解 Formik 如何构建自身非常重要,这样您才能全面掌握其可能性,并对它的工作原理有一个完整的认知模型。

一个简单的新闻通讯注册表单

假设我们想为博客添加一个新闻通讯注册表单。首先,我们的表单将只有一个名为 email 的字段。使用 Formik,只需要几行代码。

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 submitted
const formik = useFormik({
initialValues: {
email: '',
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
<button type="submit">Submit</button>
</form>
);
};

我们将表单的 initialValues 和一个提交函数 (onSubmit) 传递给 useFormik() Hook。然后,该 Hook 将表单状态和辅助方法的“礼包”返回给我们,我们将其称为 formik。目前,我们只关心以下辅助方法

  • handleSubmit:提交处理程序
  • handleChange:传递给每个 <input><select><textarea> 的更改处理程序
  • values:表单的当前值

如上所示,我们将这些值分别传递给其各自的 props……就是这样!我们现在可以使用 Formik 来拥有一个可工作的表单。我们不必自己管理表单的值,也不必为每个输入编写自定义事件处理程序,而只需使用 useFormik() 即可。

这很不错,但是只有一个输入框,使用 useFormik() 的好处就不明显了。所以让我们再添加两个输入框:一个用于用户的姓氏和名字,我们将在表单中将其存储为 firstNamelastName

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>
<input
id="firstName"
name="firstName"
type="text"
onChange={formik.handleChange}
value={formik.values.firstName}
/>
<label htmlFor="lastName">Last Name</label>
<input
id="lastName"
name="lastName"
type="text"
onChange={formik.handleChange}
value={formik.values.lastName}
/>
<label htmlFor="email">Email Address</label>
<input
id="email"
name="email"
type="email"
onChange={formik.handleChange}
value={formik.values.email}
/>
<button type="submit">Submit</button>
</form>
);
};

如果您仔细查看我们的新代码,您会注意到一些模式和对称性正在形成

  1. 我们对每个 HTML 输入框都重复使用相同的更改处理程序函数 handleChange
  2. 我们传递一个 idname HTML 属性,它们与我们在 initialValues 中定义的属性匹配
  3. 我们使用相同的名称访问字段的值 (email -> formik.values.email)

如果您熟悉使用纯 React 构建表单,您可以将 Formik 的 handleChange 视为这样工作

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
});
}

验证

虽然我们的联系表单可以工作,但它还没有完全具备功能;用户可以提交它,但它不会告诉他们哪些(如果有)字段是必填的。

如果我们没问题使用浏览器内置的 HTML 输入验证,我们可以为每个输入添加 required 属性,指定最小/最大长度 (maxlengthminlength),或者为这些输入中的每一个添加一个用于正则表达式验证的 pattern 属性。如果我们可以用这些属性解决问题,那就太好了。但是,HTML 验证有其局限性。首先,它只在浏览器中有效!因此,这显然不适用于 React Native。其次,向用户显示自定义错误消息很困难/不可能。第三,它非常笨拙。

如前所述,Formik 不仅跟踪表单的 values,还跟踪其验证和错误消息。若要使用 JS 添加验证,让我们指定一个自定义验证函数,并将其作为 validate 传递给 useFormik() Hook。如果存在错误,此自定义验证函数应生成一个与我们的 values/initialValues 形状匹配的 error 对象。再次……对称性……是的……

import React from 'react';
import { useFormik } from 'formik';
// A custom validation function. This must return an object
// which keys are symmetrical to our values/initialValues
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 = () => {
// 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 submitted
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>
<input
id="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>
<input
id="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>
<input
id="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 将在每次击键(更改事件)、每个输入的失焦事件以及提交之前进行验证。我们传递给 useFormik()onSubmit 函数仅在没有错误时(即如果我们的 validate 函数返回 {})才会执行。

已访问的字段

虽然我们的表单可以工作,用户也能看到每个错误,但对于他们来说这并不是很好的用户体验。由于我们的验证函数在每次按键时都会针对整个表单的values进行运行,因此我们的errors对象在任何给定时刻都包含所有验证错误。在我们的组件中,我们只是检查是否存在错误,然后立即将其显示给用户。这很尴尬,因为我们将显示用户甚至还没有访问过的字段的错误消息。大多数情况下,我们只希望在用户完成在该字段中输入内容之后才显示该字段的错误消息。

errorsvalues类似,Formik 会跟踪哪些字段已被访问过。它将此信息存储在一个名为touched的对象中,该对象也反映了values/initialValues的结构。touched的键是字段名称,touched的值是布尔值true/false

为了利用touched,我们将formik.handleBlur传递给每个输入的onBlur属性。此函数的工作原理类似于formik.handleChange,它使用name属性来确定要更新哪个字段。

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>
<input
id="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>
<input
id="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>
<input
id="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,我们可以将错误消息渲染逻辑更改为在给定字段的错误消息存在用户已访问该字段时才显示。

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>
<input
id="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>
<input
id="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>
<input
id="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>
);
};

使用 Yup 进行 Schema 验证

如上所示,验证留给您自己处理。您可以随意编写自己的验证器或使用第三方辅助库。Formik 的作者/其大部分用户使用Jason Quense的库Yup进行对象 Schema 验证。Yup 的 API 类似于JoiReact PropTypes,但它也足够小,可以用于浏览器,并且速度足够快,可以用于运行时使用。您可以使用此REPL进行尝试。

由于 Formik 作者/用户非常喜欢 Yup,因此 Formik 为 Yup 提供了一个特殊的配置属性validationSchema,它会自动将 Yup 的验证错误消息转换为一个漂亮的对象,其键与values/initialValues/touched匹配(就像任何自定义验证函数都必须做的那样)。无论如何,您可以像这样从 NPM/yarn 安装 Yup...

npm install yup --save
# or via yarn
yarn add yup

要了解 Yup 的工作原理,让我们删除自定义验证函数validate,并使用 Yup 和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>
<input
id="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>
<input
id="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>
<input
id="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>
);
};

再说一次,Yup 是完全可选的。但是,我们建议您尝试一下。如上所示,我们仅用 10 行代码而不是 30 行代码表达了完全相同的验证函数。Formik 的核心设计原则之一是帮助您保持组织。Yup 在这方面绝对很有帮助——Schema 表达性极强,直观(因为它们反映了您的值)且可重用。无论您是否使用 Yup,我们都强烈建议您在整个应用程序中共享常用的验证方法。这将确保通用字段(例如电子邮件、街道地址、用户名、电话号码等)得到一致的验证,并带来更好的用户体验。

减少样板代码

getFieldProps()

上面的代码非常明确地说明了 Formik 正在执行的操作。onChange -> handleChangeonBlur -> handleBlur,依此类推。但是,为了节省您的时间,useFormik()返回了一个名为formik.getFieldProps()的辅助方法,以便更快地连接输入。给定一些字段级信息,它会为您返回给定字段的onChangeonBlurvaluechecked的精确组。然后,您可以将其扩展到inputselecttextarea上。

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>
<input
id="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>
);
};

利用 React Context

我们上面的代码再次非常明确地说明了 Formik 正在执行的操作。onChange -> handleChangeonBlur -> handleBlur,依此类推。但是,我们仍然必须手动将每个输入传递给此“prop 获取器”getFieldProps()。为了节省更多时间,Formik 附带了React Context驱动的 API/组件,以简化生活并减少代码冗长:<Formik /><Form /><Field /><ErrorMessage />。更明确地说,它们隐式地使用 React Context 来连接父<Formik />状态/方法。

由于这些组件使用 React Context,因此我们需要渲染一个React Context Provider,在我们的树中保存我们的表单状态和辅助函数。如果您自己这样做,它将如下所示

import React from 'react';
import { useFormik } from 'formik';
// Create empty context
const FormikContext = React.createContext({});
// Place all of what’s returned by useFormik into context
export const Formik = ({ children, ...props }) => {
const formikStateAndHelpers = useFormik(props);
return (
<FormikContext.Provider value={formikStateAndHelpers}>
{typeof children === 'function'
? children(formikStateAndHelpers)
: children}
</FormikContext.Provider>
);
};

幸运的是,我们已经在<Formik>组件中为您完成了此操作,该组件的工作原理与此完全相同。

现在让我们将useFormik()hook 替换为 Formik 的<Formik>组件/render-prop。由于它是一个组件,我们将传递给useFormik()的对象转换为 JSX,每个键都成为一个属性。

import React from 'react';
import { Formik } from 'formik';
import * as Yup from 'yup';
const SignupForm = () => {
return (
<Formik
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, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 400);
}}
>
{formik => (
<form onSubmit={formik.handleSubmit}>
<label htmlFor="firstName">First Name</label>
<input
id="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>
)}
</Formik>
);
};

如上所示,我们替换了useFormik()hook 并将其替换为<Formik>组件。<Formik>组件接受一个函数作为其子元素(也称为render prop)。它的参数与useFormik()返回的完全相同对象(实际上,<Formik>在内部调用useFormik()!)。因此,我们的表单与之前的工作原理相同,只是现在我们可以使用新组件以更简洁的方式表达自己。

import React from 'react';
import { Formik, Field, Form, ErrorMessage } from 'formik';
import * as Yup from 'yup';
const SignupForm = () => {
return (
<Formik
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, { 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属性的情况下,会隐式地获取相应的onChangeonBlurvalue属性并将它们传递给元素以及您传递给它的任何属性。但是,由于并非所有内容都是输入,因此<Field>还接受一些其他属性,让您可以渲染任何您想要的内容。一些例子..

// <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!请查看...

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 these
const SignupForm = () => {
return (
<>
<h1>Subscribe!</h1>
<Formik
initialValues={{
firstName: '',
lastName: '',
email: '',
acceptedTerms: false, // added for our checkbox
jobType: '', // 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>
<MyTextInput
label="First Name"
name="firstName"
type="text"
placeholder="Jane"
/>
<MyTextInput
label="Last Name"
name="lastName"
type="text"
placeholder="Doe"
/>
<MyTextInput
label="Email Address"
name="email"
type="email"
placeholder="[email protected]"
/>
<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>一样。我们可以使用它来构建一组满足我们需求的可重用输入。

总结

恭喜!您已使用 Formik 创建了一个注册表单,该表单

  • 具有复杂的验证逻辑和丰富的错误消息
  • 在正确的时间(在他们离开某个字段后)正确地向用户显示错误消息
  • 利用您自己的自定义输入组件,您可以在应用程序中的其他表单上使用它们

干得好!我们希望您现在对 Formik 的工作原理有了很好的了解。

在此处查看最终结果:最终结果

如果您有额外的时间或想练习您的新 Formik 技能,以下是一些您可以对注册表单进行改进的想法,这些想法按难度递增的顺序列出

  • 在用户尝试提交时禁用提交按钮(提示:formik.isSubmitting)。
  • 使用formik.handleReset<button type="reset">添加重置按钮。
  • 根据 URL 查询字符串或传递给<SignupForm>的属性预填充initialValues
  • 当字段有错误且未聚焦时,将输入边框颜色更改为红色。
  • 在字段显示错误并已被访问时,向每个字段添加抖动动画。
  • 将表单状态持久化到浏览器的sessionStorage中,以便在页面刷新之间保留表单进度。

在本教程中,我们介绍了 Formik 的概念,包括表单状态、字段、验证、hook、render prop 和 React context。有关每个主题的更详细说明,请查看其余的文档。要了解有关在本教程中定义组件和 hook 的更多信息,请查看API 参考

此页面是否有帮助?

订阅我们的时事通讯

最新的 Formik 新闻、文章和资源,发送到您的收件箱。

版权所有 © 2020 Formium, Inc. 保留所有权利。