id: validation title: 验证

custom_edit_url: https://github.com/jaredpalmer/formik/edit/master/docs/guides/validation.md

Formik 旨在轻松管理具有复杂验证的表单。Formik 支持同步和异步,表单级和字段级验证。此外,它还通过 Yup ,提供基于模式的表单级验证支持。本指南将描述上述所有内容的来龙去脉。

验证的味道

表单级验证

表单级验证非常有用,因为您通过values和 props,可以完全访问所有表单,并且只要函数运行,这样您就可以同时验证依赖字段。

有两种方法可以使用 Formik 进行表单级验证:

  • <Formik validate>withFormik({ validate: ... })
  • <Formik validationSchema>withFormik({ validationSchema: ... })

validate

<Formik>withFormik()采取 prop/option 调用validate,接受同步或异步函数。

  1. // Synchronous validation
  2. const validate = (values, props /* only available when using withFormik */) => {
  3. let errors = {};
  4. if (!values.email) {
  5. errors.email = 'Required';
  6. } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
  7. errors.email = 'Invalid email address';
  8. }
  9. //...
  10. return errors;
  11. };
  12. // Async Validation
  13. const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
  14. const validate = (values, props /* only available when using withFormik */) => {
  15. return sleep(2000).then(() => {
  16. let errors = {};
  17. if (['admin', 'null', 'god'].includes(values.username)) {
  18. errors.username = 'Nice try';
  19. }
  20. // ...
  21. if (Object.keys(errors).length) {
  22. throw errors;
  23. }
  24. });
  25. };

有关<Formik validate>的更多信息,请参阅 API 参考。

validationSchema

如您所见,验证交由你决定。您可以随意编写自己的验证器或使用第三方库。在 The Palmer 集团,我们使用Yup,来完成对象模式验证。它与反应 PropTypes有非常类似的 API,但是对浏览器而言足够小,并且对于运行引擎的使用来说足够快。因为我们:heart: Yup sooo much,Formik 有一个特殊的配置 option/ prop 用于 Yup 对象模式,叫作validationSchema,它自动将 Yup 的验证错误转换为 key 匹配valuestouched的漂亮对象。这种对称性使得可以轻松地围绕错误消息,管理业务逻辑。

要将 Yup 添加到项目中,请从 NPM 安装它。

  1. npm install yup --save
  2. # typescript 用户应添加 @types/yup
  1. import React from 'react';
  2. import {Formik, Form, Field} from 'formik';
  3. import * as Yup from 'yup';
  4. const SignupSchema = Yup.object().shape({
  5. firstName: Yup.string()
  6. .min(2, 'Too Short!')
  7. .max(50, 'Too Long!')
  8. .required('Required'),
  9. lastName: Yup.string()
  10. .min(2, 'Too Short!')
  11. .max(50, 'Too Long!')
  12. .required('Required'),
  13. email: Yup.string()
  14. .email('Invalid email')
  15. .required('Required')
  16. });
  17. export const ValidationSchemaExample = () => (
  18. <div>
  19. <h1>Signup</h1>
  20. <Formik
  21. initialValues={{
  22. firstName: '',
  23. lastName: '',
  24. email: ''
  25. }}
  26. validationSchema={SignupSchema}
  27. onSubmit={values => {
  28. // same shape as initial values
  29. console.log(values);
  30. }}
  31. >
  32. {({errors, touched}) => (
  33. <Form>
  34. <Field name="firstName" />
  35. {errors.firstName && touched.firstName ? (
  36. <div>{errors.firstName}</div>
  37. ) : null}
  38. <Field name="lastName" />
  39. {errors.lastName && touched.lastName ? (
  40. <div>{errors.lastName}</div>
  41. ) : null}
  42. <Field name="email" type="email" />
  43. {errors.email && touched.email ? <div>{errors.email}</div> : null}
  44. <button type="submit">Submit</button>
  45. </Form>
  46. )}
  47. </Formik>
  48. </div>
  49. );

有关<Formik validationSchema>的更多信息,请参阅 API 参考。

字段级验证

validate

Formik 通过<Field>/<FastField>组件的validate支持字段级验证。此函数可以是同步的或异步的(返回 Promise)。默认情况下它将在任一onChangeonBlur之后运行。此行为可让分别使用validateOnChangevalidateOnBlurprops 的顶层<Formik/>组件改变。除了 change/blur 之外,所有字段级验证都在尝试提交开始时运行,然后结果与任何顶级验证结果深度合并。

注意:<Field>/<FastField>组件’validate函数只能在已装载的字段上执行。也就是说,如果您的任何字段在表单流程中卸载(例如 Material-UI 的<Tabs>卸下前一个您的用户开启的<Tab>),在表单验证/提交期间不会验证这些字段。

  1. import React from 'react';
  2. import {Formik, Form, Field} from 'formik';
  3. import * as Yup from 'yup';
  4. function validateEmail(value) {
  5. let error;
  6. if (!value) {
  7. error = 'Required';
  8. } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)) {
  9. error = 'Invalid email address';
  10. }
  11. return error;
  12. }
  13. function validateUsername(value) {
  14. let error;
  15. if (value === 'admin') {
  16. error = 'Nice try!';
  17. }
  18. return error;
  19. }
  20. export const FieldLevelValidationExample = () => (
  21. <div>
  22. <h1>Signup</h1>
  23. <Formik
  24. initialValues={{
  25. username: '',
  26. email: ''
  27. }}
  28. onSubmit={values => {
  29. // same shape as initial values
  30. console.log(values);
  31. }}
  32. >
  33. {({errors, touched, isValidating}) => (
  34. <Form>
  35. <Field name="email" validate={validateEmail} />
  36. {errors.email && touched.email && <div>{errors.email}</div>}
  37. <Field name="username" validate={validateUsername} />
  38. {errors.username && touched.username && <div>{errors.username}</div>}
  39. <button type="submit">Submit</button>
  40. </Form>
  41. )}
  42. </Formik>
  43. </div>
  44. );

手动触发验证

您可以使用 Formik 手动触发,表单级和字段级验证,分别为validateFormvalidateField方法。

  1. import React from 'react';
  2. import { Formik, Form, Field } from 'formik';
  3. function validateEmail(value) {
  4. let error;
  5. if (!value) {
  6. error = 'Required';
  7. } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value)) {
  8. error = 'Invalid email address';
  9. }
  10. return error;
  11. }
  12. function validateUsername(value) {
  13. let error;
  14. if (value === 'admin') {
  15. error = 'Nice try!';
  16. }
  17. return error;
  18. }
  19. export const FieldLevelValidationExample = () => (
  20. <div>
  21. <h1>Signup</h1>
  22. <Formik
  23. initialValues={{
  24. username: '',
  25. email: '',
  26. }}
  27. onSubmit={values => {
  28. // same shape as initial values
  29. console.log(values);
  30. }}
  31. >
  32. {({ errors, touched, validateField, validateForm }) => (
  33. <Form>
  34. <Field name="email" validate={validateEmail} />
  35. {errors.email && touched.email && <div>{errors.email}</div>}
  36. <Field name="username" validate={validateUsername} />
  37. {errors.username && touched.username && <div>{errors.username}</div>}
  38. {/** Trigger field-level validation
  39. imperatively */}
  40. <button type="button" onClick={() => validateField('username')}>
  41. Check Username
  42. </button>
  43. {/** Trigger form-level validation
  44. imperatively */}
  45. <button type="button" onClick={() => validateForm().then(() => console.log('blah')))}>
  46. Validate All
  47. </button>
  48. <button type="submit">Submit</button>
  49. </Form>
  50. )}
  51. </Formik>
  52. </div>
  53. );

验证何时运行?

您可以通过更改<Formik validateOnChange>和/或<Formik validateOnBlur>props 的值,来控制 Formik 何时运行验证,这都取决于你的需要。默认情况下,Formik 将运行以下验证方法:

“change”事件/方法之后(更新values

  • handleChange
  • setFieldValue
  • setValues

在“blur”事件/方法之后(更新touched

  • handleBlur
  • setTouched
  • setFieldTouched

每当试图提交时

  • handleSubmit
  • submitForm

还通过 Formik 的呈现/注入 props 向您提供了必要的助手方法,您可以使用这些方法强制调用验证。

  • validateForm
  • validateField

显示错误消息

@todo

常见问题

如何确定表单是否正在验证? 如果isValidatingprop 是true
我可以返回“null”作为错误消息吗? 不,使用undefined。Formik 使用undefined表示空状态。如果你使用null,Formik 计算出的几个部分(例如isValid),将无法按预期工作。
如何测试验证? formik 有大量的单元测试用于 yup 验证,因此您不需要测试它。但是,如果您正在滚动自己的验证函数,那么您应该简单地对它们进行单元测试。如果您确实需要测试 formik 的执行情况,您应该分别使用指令式validateFormvalidateField方法。