Formik 旨在轻松管理具有复杂验证的表单。Formik 支持同步和异步表单级和字段级验证。此外,它还通过 Yup 内置了对基于模式的表单级验证的支持。本指南将描述上述所有内容的来龙去脉。
¥Formik is designed to manage forms with complex validation with ease. Formik supports synchronous and asynchronous form-level and field-level validation. Furthermore, it comes with baked-in support for schema-based form-level validation through Yup. This guide will describe the ins and outs of all of the above.
¥Flavors of Validation
¥Form-level Validation
表单级验证非常有用,因为只要函数运行,你就可以完全访问表单的所有 values
和 props,因此你可以同时验证依赖字段。
¥Form-level validation is useful because you have complete access to all of your form's values
and props whenever the function runs, so you can validate dependent fields at the same time.
使用 Formik 进行表单级验证有两种方法:
¥There are 2 ways to do form-level validation with Formik:
<Formik validate>
和 withFormik({ validate: ... })
¥<Formik validate>
and withFormik({ validate: ... })
<Formik validationSchema>
和 withFormik({ validationSchema: ... })
¥<Formik validationSchema>
and withFormik({ validationSchema: ... })
validate
<Formik>
和 withFormik()
采用名为 validate
的属性/选项,它接受同步或异步函数。
¥<Formik>
and withFormik()
take a prop/option called validate
that accepts either a synchronous or asynchronous function.
// Synchronous validationconst validate = (values, props /* only available when using withFormik */) => {const errors = {};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;};// Async Validationconst sleep = ms => new Promise(resolve => setTimeout(resolve, ms));const validate = (values, props /* only available when using withFormik */) => {return sleep(2000).then(() => {const errors = {};if (['admin', 'null', 'god'].includes(values.username)) {errors.username = 'Nice try';}// ...return errors;});};
有关 <Formik validate>
的更多信息,请参阅 API 参考。
¥For more information about <Formik validate>
, see the API reference.
validationSchema
正如你在上面看到的,验证由你决定。你可以随意编写自己的验证器或使用第三方库。在 Palmer Group,我们使用 是的 进行对象模式验证。它有一个与 Joi 和 React PropTypes 非常相似的 API,但对于浏览器来说足够小,对于运行时使用来说足够快。因为我们❤️ Yup 太多了,Formik 有一个名为 validationSchema
的 Yup 对象模式的特殊配置选项/属性,它会自动将 Yup 的验证错误转换为一个漂亮的对象,其键与 values
和 touched
匹配。这种对称性使得管理围绕错误消息的业务逻辑变得容易。
¥As you can see above, validation is left up to you. Feel free to write your own
validators or use a 3rd party library. At The Palmer Group, we use
Yup for object schema validation. It has an
API that's pretty similar to Joi and
React PropTypes but is small enough
for the browser and fast enough for runtime usage. Because we ❤️ Yup sooo
much, Formik has a special config option / prop for Yup object schemas called validationSchema
which will automatically transform Yup's validation errors into a pretty object whose keys match values
and touched
. This symmetry makes it easy to manage business logic around error messages.
要将 Yup 添加到你的项目中,请从 NPM 安装它。
¥To add Yup to your project, install it from NPM.
npm install yup --save
import React from 'react';import { Formik, Form, Field } from 'formik';import * as Yup from 'yup';const SignupSchema = Yup.object().shape({firstName: Yup.string().min(2, 'Too Short!').max(50, 'Too Long!').required('Required'),lastName: Yup.string().min(2, 'Too Short!').max(50, 'Too Long!').required('Required'),email: Yup.string().email('Invalid email').required('Required'),});export const ValidationSchemaExample = () => (<div><h1>Signup</h1><FormikinitialValues={{firstName: '',lastName: '',email: '',}}validationSchema={SignupSchema}onSubmit={values => {// same shape as initial valuesconsole.log(values);}}>{({ errors, touched }) => (<Form><Field name="firstName" />{errors.firstName && touched.firstName ? (<div>{errors.firstName}</div>) : null}<Field name="lastName" />{errors.lastName && touched.lastName ? (<div>{errors.lastName}</div>) : null}<Field name="email" type="email" />{errors.email && touched.email ? <div>{errors.email}</div> : null}<button type="submit">Submit</button></Form>)}</Formik></div>);
有关 <Formik validationSchema>
的更多信息,请参阅 API 参考。
¥For more information about <Formik validationSchema>
, see the API reference.
¥Field-level Validation
validate
Formik 通过 <Field>
/<FastField>
组件的 validate
属性或 useField
钩子支持字段级验证。该函数可以是同步的或异步的(返回 Promise)。默认情况下,它将在任何 onChange
和 onBlur
之后运行。可以分别使用 validateOnChange
和 validateOnBlur
属性在顶层 <Formik/>
组件中更改此行为。除了更改/模糊之外,所有字段级验证都在提交尝试开始时运行,然后结果与任何顶层验证结果深度合并。
¥Formik supports field-level validation via the validate
prop of <Field>
/<FastField>
components or useField
hook. This function can be synchronous or asynchronous (return a Promise). It will run after any onChange
and onBlur
by default. This behavior can be altered at the top level <Formik/>
component using the validateOnChange
and validateOnBlur
props respectively. In addition to change/blur, all field-level validations are run at the beginning of a submission attempt and then the results are deeply merged with any top-level validation results.
注意:
<Field>/<FastField>
组件的validate
功能将仅在已安装的字段上执行。也就是说,如果你的任何字段在表单流程期间卸载(例如,Material-UI 的<Tabs>
卸载了用户之前使用的<Tab>
),则在表单验证/提交期间将不会验证这些字段。¥Note: The
<Field>/<FastField>
components'validate
function will only be executed on mounted fields. That is to say, if any of your fields unmount during the flow of your form (e.g. Material-UI's<Tabs>
unmounts the previous<Tab>
your user was on), those fields will not be validated during form validation/submission.
import React from 'react';import { Formik, Form, Field } from 'formik';function validateEmail(value) {let error;if (!value) {error = 'Required';} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)) {error = 'Invalid email address';}return error;}function validateUsername(value) {let error;if (value === 'admin') {error = 'Nice try!';}return error;}export const FieldLevelValidationExample = () => (<div><h1>Signup</h1><FormikinitialValues={{username: '',email: '',}}onSubmit={values => {// same shape as initial valuesconsole.log(values);}}>{({ errors, touched, isValidating }) => (<Form><Field name="email" validate={validateEmail} />{errors.email && touched.email && <div>{errors.email}</div>}<Field name="username" validate={validateUsername} />{errors.username && touched.username && <div>{errors.username}</div>}<button type="submit">Submit</button></Form>)}</Formik></div>);
¥Manually Triggering Validation
你可以分别使用 validateForm
和 validateField
方法通过 Formik 手动触发表单级和字段级验证。
¥You can manually trigger both form-level and field-level validation with Formik using the validateForm
and validateField
methods respectively.
import React from 'react';import { Formik, Form, Field } from 'formik';function validateEmail(value) {let error;if (!value) {error = 'Required';} else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)) {error = 'Invalid email address';}return error;}function validateUsername(value) {let error;if (value === 'admin') {error = 'Nice try!';}return error;}export const FieldLevelValidationExample = () => (<div><h1>Signup</h1><FormikinitialValues={{username: '',email: '',}}onSubmit={values => {// same shape as initial valuesconsole.log(values);}}>{({ errors, touched, validateField, validateForm }) => (<Form><Field name="email" validate={validateEmail} />{errors.email && touched.email && <div>{errors.email}</div>}<Field name="username" validate={validateUsername} />{errors.username && touched.username && <div>{errors.username}</div>}{/** Trigger field-level validationimperatively */}<button type="button" onClick={() => validateField('username')}>Check Username</button>{/** Trigger form-level validationimperatively */}<buttontype="button"onClick={() => validateForm().then(() => console.log('blah'))}>Validate All</button><button type="submit">Submit</button></Form>)}</Formik></div>);
¥When Does Validation Run?
你可以根据需要更改 <Formik validateOnChange>
和/或 <Formik validateOnBlur>
属性的值来控制 Formik 何时运行验证。默认情况下,Formik 将运行验证方法,如下所示:
¥You can control when Formik runs validation by changing the values of <Formik validateOnChange>
and/or <Formik validateOnBlur>
props depending on your needs. By default, Formik will run validation methods as follows:
在 "change" 事件/方法之后(更新 values
的事情)
¥After "change" events/methods (things that updatevalues
)
handleChange
setFieldValue
setValues
"blur" 事件/方法之后(更新 touched
的事情)
¥After "blur" events/methods (things that update touched
)
handleBlur
setTouched
setFieldTouched
每当尝试提交时
¥Whenever submission is attempted
handleSubmit
submitForm
还通过 Formik 的 render/injected props 为你提供了命令式帮助方法,你可以使用它们来命令式调用验证。
¥There are also imperative helper methods provided to you via Formik's render/injected props which you can use to imperatively call validation.
validateForm
validateField
¥Displaying Error Messages
错误消息取决于表单的验证。如果存在错误,并且验证函数会生成一个错误对象(应该如此),其形状与我们的值/初始值相匹配,则可以从错误对象访问依赖字段错误。
¥Error messages are dependent on the form's validation. If an error exists, and the validation function produces an error object (as it should) with a matching shape to our values/initialValues, dependent field errors can be accessed from the errors object.
import React from 'react';import { Formik, Form, Field } from 'formik';import * as Yup from 'yup';const DisplayingErrorMessagesSchema = Yup.object().shape({username: Yup.string().min(2, 'Too Short!').max(50, 'Too Long!').required('Required'),email: Yup.string().email('Invalid email').required('Required'),});export const DisplayingErrorMessagesExample = () => (<div><h1>Displaying Error Messages</h1><FormikinitialValues={{username: '',email: '',}}validationSchema={DisplayingErrorMessagesSchema}onSubmit={values => {// same shape as initial valuesconsole.log(values);}}>{({ errors, touched }) => (<Form><Field name="username" />{/* If this field has been touched, and it contains an error, display it*/}{touched.username && errors.username && <div>{errors.username}</div>}<Field name="email" />{/* If this field has been touched, and it contains an error, displayit */}{touched.email && errors.email && <div>{errors.email}</div>}<button type="submit">Submit</button></Form>)}</Formik></div>);
ErrorMessage 组件还可用于显示错误消息。
¥The ErrorMessage component can also be used to display error messages.
¥Frequently Asked Questions
如果 isValidating
属性是 true
¥If isValidating
prop is true
不。请改用 undefined
。Formik 使用 undefined
来表示空状态。如果你使用 null
,Formik 的计算属性的几个部分(例如 isValid
)将无法按预期工作。
¥No. Use undefined
instead. Formik uses undefined
to represent empty states. If you use null
, several parts of Formik's computed props (e.g. isValid
for example), will not work as expected.
Formik 有大量用于 Yup 验证的单元测试,因此你无需对其进行测试。但是,如果你正在滚动自己的验证函数,则应该简单地对它们进行单元测试。如果你确实需要测试 Formik 的执行情况,你应该分别使用命令式 validateForm
和 validateField
方法。
¥Formik has extensive unit tests for Yup validation so you do not need to test that. However, if you are rolling your own validation functions, you should simply unit test those. If you do need to test Formik's execution you should use the imperative validateForm
and validateField
methods respectively.