使用Formik轻松开发更高质量的React表单(二)使用指南

前端之家收集整理的这篇文章主要介绍了使用Formik轻松开发更高质量的React表单(二)使用指南前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

一个基本的例子

设想你要开发一个可以编辑用户数据的表单。不过,你的用户API端使用了具有类似下面的嵌套对象表达:

  1. {
  2. id: string,email: string,social: {
  3. facebook: string,twitter: string,// ...
  4. }
  5. }

最后,我们想使开发的对话框表单能够接收下面几个属性(props):user,updateUser和onClose(显然,user是一个对象,updateUser和onClose却都是两个方法)。

  1. // User.js
  2. import React from 'react';
  3. import Dialog from 'MySuperDialog';
  4. import { Formik } from 'formik';
  5.  
  6. const EditUserDialog = ({ user,updateUser,onClose }) => {
  7. return (
  8. <Dialog onClose={onClose}>
  9. <h1>Edit User</h1>
  10. <Formik
  11. initialValues={user /** { email,social } */}
  12. onSubmit={(values,actions) => {
  13. CallMyApi(user.id,values).then(
  14. updatedUser => {
  15. actions.setSubmitting(false);
  16. updateUser(updatedUser),onClose();
  17. },error => {
  18. actions.setSubmitting(false);
  19. actions.setErrors(transformMyAPIErrorToAnObject(error));
  20. }
  21. );
  22. }}
  23. render={({
  24. values,errors,touched,handleBlur,handleChange,handleSubmit,isSubmitting,}) => (
  25. <form onSubmit={handleSubmit}>
  26. <input
  27. type="email"
  28. name="email"
  29. onChange={handleChange}
  30. onBlur={handleBlur}
  31. value={values.email}
  32. />
  33. {errors.email && touched.email && <div>{errors.email}</div>}
  34. <input
  35. type="text"
  36. name="social.facebook"
  37. onChange={handleChange}
  38. onBlur={handleBlur}
  39. value={values.social.facebook}
  40. />
  41. {errors.social &&
  42. errors.social.facebook &&
  43. touched.facebook && <div>{errors.social.facebook}</div>}
  44. <input
  45. type="text"
  46. name="social.twitter"
  47. onChange={handleChange}
  48. onBlur={handleBlur}
  49. value={values.social.twitter}
  50. />
  51. {errors.social &&
  52. errors.social.twitter &&
  53. touched.twitter && <div>{errors.social.twitter}</div>}
  54. <button type="submit" disabled={isSubmitting}>
  55. Submit
  56. </button>
  57. </form>
  58. )}
  59. />
  60. </Dialog>
  61. );
  62. };

简化编码

为了简化表单组件的编码,Formik还提供了两个帮助API:


  • <Field>

  • <Form />


于是,下面的代码与前面一致,只是使用<Form />和<Field />这两个API进行了改写:

  1. // EditUserDialog.js
  2. import React from 'react';
  3. import Dialog from 'MySuperDialog';
  4. import { Formik,Field,Form } from 'formik';
  5.  
  6. const EditUserDialog = ({ user,error => {
  7. actions.setSubmitting(false);
  8. actions.setErrors(transformMyAPIErrorToAnObject(error));
  9. }
  10. );
  11. }}
  12. render={({ errors,isSubmitting }) => (
  13. <Form>
  14. <Field type="email" name="email" />
  15. {errors.email && touched.social.email && <div>{errors.email}</div>}
  16. <Field type="text" name="social.facebook" />
  17. {errors.social.facebook &&
  18. touched.social.facebook && <div>{errors.social.facebook}</div>}
  19. <Field type="text" name="social.twitter" />
  20. {errors.social.twitter &&
  21. touched.social.twitter && <div>{errors.social.twitter}</div>}
  22. <button type="submit" disabled={isSubmitting}>
  23. Submit
  24. </button>
  25. </Form>
  26. )}
  27. />
  28. </Dialog>
  29. );
  30. };

React Native开发问题


Formik与React Native 和React Native Web开发完全兼容。然而,由于ReactDOM和React Native表单处理与文本输入方式的不同,有两个区别值得注意。本文将介绍这个问题并推荐更佳使用方式。

在进一步讨论前,先来最简要地概括一下如何在React Native中使用Formik。下面的轮廓代码展示了两者的关键区别:

  1. // Formik +React Native示例
  2. import React from 'react';
  3. import { Button,TextInput,View } from 'react-native';
  4. import { withFormik } from 'formik';
  5.  
  6. const enhancer = withFormik({
  7. /*...*/
  8. });
  9.  
  10. const MyReactNativeForm = props => (
  11. <View>
  12. <TextInput
  13. onChangeText={props.handleChange('email')}
  14. onBlur={props.handleBlur('email')}
  15. value={props.values.email}
  16. />
  17. <Button onPress={props.handleSubmit} title="Submit" />
  18. </View>
  19. );
  20.  
  21. export default enhancer(MyReactNativeForm);

从上面代码中,你会明显注意到在React Native 和React DOM开发中使用Formik存在如下不同:


(1)Formik的props.handleSubmit被传递给一个<Button onPress={...} />,而不是HTML <form onSubmit={...} /> 组件(因为在React Native中没有<form />元素)。

(2)<TextInput />使用Formik的props.handleChange(fieldName)和handleBlur(fieldName),而不是直接把回调函数赋值给props,因为我们必须从某处得到fieldName,而在ReactNative中我们无法你在Web中一样自动获取它(使用input的name属性)。作为可选方案,你还可以使用 setFieldValue(fieldName,value) 和setTouched(fieldName,bool) 这两个函数


避免在render中创建新函数

如果因某种原因你想在每一个render中避免创建新函数,那么我建议你把React Native的 <TextInput /> 当作它是一个第三方提供的定制输入元素:


  • 编写你自己的针对定制输入元素的类包装器;
  • 传递定制组件的props.setFieldValue,而不是传递props.handleChange;
  • 使用一个定制的change函数回调,它将调用你传递给setFieldValue的任何内容

请参考下面的代码

  1. // FormikReactNativeTextInput.js
  2. import * as React from 'react';
  3. import { TextInput } from 'react-native';
  4.  
  5. export default class FormikReactNativeTextInput extends React.Component {
  6. handleChange = (value: string) => {
  7. // remember that onChangeText will be Formik's setFieldValue
  8. this.props.onChangeText(this.props.name,value);
  9. };
  10.  
  11. render() {
  12. // we want to pass through all the props except for onChangeText
  13. const { onChangeText,...otherProps } = this.props;
  14. return (
  15. <TextInput
  16. onChangeText={this.handleChange}
  17. {...otherProps} // IRL,you should be more explicit when using TS
  18. />
  19. );
  20. }
  21. }

然后,你可以像下面这样使用这个定制输入组件:

  1. // MyReactNativeForm.js
  2. import { View,Button } from 'react-native';
  3. import TextInput from './FormikReactNativeTextInput';
  4. import { Formik } from 'formik';
  5.  
  6. const MyReactNativeForm = props => (
  7. <View>
  8. <Formik
  9. onSubmit={(values,actions) => {
  10. setTimeout(() => {
  11. console.log(JSON.stringify(values,null,2));
  12. actions.setSubmitting(false);
  13. },1000);
  14. }}
  15. render={props => (
  16. <View>
  17. <TextInput
  18. name="email"
  19. onChangeText={props.setFieldValue}
  20. value={props.values.email}
  21. />
  22. <Button title="submit" onPress={props.handleSubmit} />
  23. </View>
  24. )}
  25. />
  26. </View>
  27. );
  28.  
  29. export default MyReactNativeForm;

使用TypeScript开发Formik表单

(一)TypeScript类型

Formik是使用TypeScript写的,Formik中的类型十分类似于React Router 4中的<Route>。

  1. Render props (<Formik /> and <Field />)
  2. import * as React from 'react';
  3. import { Formik,FormikProps,Form,FieldProps } from 'formik';
  4.  
  5. interface MyFormValues {
  6. firstName: string;
  7. }
  8.  
  9. export const MyApp: React.SFC<{} /* whatever */> = () => {
  10. return (
  11. <div>
  12. <h1>My Example</h1>
  13. <Formik
  14. initialValues={{ firstName: '' }}
  15. onSubmit={(values: MyFormValues) => alert(JSON.stringify(values))}
  16. render={(formikBag: FormikProps<MyFormValues>) => (
  17. <Form>
  18. <Field
  19. name="firstName"
  20. render={({ field,form }: FieldProps<MyFormValues>) => (
  21. <div>
  22. <input type="text" {...field} placeholder="First Name" />
  23. {form.touched.firstName &&
  24. form.errors.firstName &&
  25. form.errors.firstName}
  26. </div>
  27. )}
  28. />
  29. </Form>
  30. )}
  31. />
  32. </div>
  33. );
  34. };

(二)使用withFormik()

  1. import React from 'react';
  2. import * as Yup from 'yup';
  3. import { withFormik,FormikErrors,Field } from 'formik';
  4.  
  5. // Shape of form values
  6. interface FormValues {
  7. email: string;
  8. password: string;
  9. }
  10.  
  11. interface OtherProps {
  12. message: string;
  13. }

顺便提醒一下,你可以使用InjectedFormikProps<OtherProps,FormValues>来代替下面的实现方式。本质上,它们是相同的,只不过InjectedFormikProps是当Formik仅输出一个HOC(高阶组件)时的代替而已。而且,这个方法灵活性差一些,因为它需要对所有属性(props)进行包装。

  1. const InnerForm = (props: OtherProps & FormikProps<FormValues>) => {
  2. const { touched,message } = props;
  3. return (
  4. <Form>
  5. <h1>{message}</h1>
  6. <Field type="email" name="email" />
  7. {touched.email && errors.email && <div>{errors.email}</div>}
  8.  
  9. <Field type="password" name="password" />
  10. {touched.password && errors.password && <div>{errors.password}</div>}
  11.  
  12. <button type="submit" disabled={isSubmitting}>
  13. Submit
  14. </button>
  15. </Form>
  16. );
  17. };
  18.  
  19. //MyForm接收的props的类型
  20. interface MyFormProps {
  21. initialEmail?: string;
  22. message: string; // if this passed all the way through you might do this or make a union type
  23. }
  24.  
  25. //使用withFormik高阶组件包装你的表单
  26. const MyForm = withFormik<MyFormProps,FormValues>({
  27. // Transform outer props into form values
  28. mapPropsToValues: props => {
  29. return {
  30. email: props.initialEmail || '',password: '',};
  31. },//添加定制的校验函数(也有可能是异步的)
  32. validate: (values: FormValues) => {
  33. let errors: FormikErrors = {};
  34. if (!values.email) {
  35. errors.email = 'required';
  36. } else if (!isValidEmail(values.email)) {
  37. errors.email = 'Invalid email address';
  38. }
  39. return errors;
  40. },handleSubmit: values => {
  41. // do submitting things
  42. },})(InnerForm);
  43.  
  44. // 你可以在任何地方使用<MyForm />
  45. const Basic = () => (
  46. <div>
  47. <h1>My App</h1>
  48. <p>This can be anywhere in your application</p>
  49. <MyForm message="Sign up" />
  50. </div>
  51. );
  52.  
  53. export default Basic;

Formik表单提交原理


要在Formik中提交表单,你需要以某种方式触发 handleSubmit(e) 或者submitForm属性调用(在Formik中这两个方法都是以属性的方式提供的)。 当调用其中一个方法时,Formik每次都会执行下面的伪代码

(一)预提交
(1)修改所有字段
(2)把isSubmitting 设置为true
(3)submitCount + 1
(二)校验
(1)把isValidating设置为true
(2)异步运行所有字段级的校验和validationSchema,并深度合并执行结果
(3)判断是否存在错误
如果存在错误:取消提交,把isValidating设置为false,设置错误信息,并把isSubmitting设置为false
如果不存在错误:Set isValidating to false,proceed to "Submission"
(三)提交
最后继续运行你的提交函数吧(例如是onSubmit或者handleSubmit)。你可以通过在你的处理器函数调用setSubmitting(false) 来结束生命周期。

FAQ



(1)Q:怎么判定提交处理器(submission handler)正在执行中?
A:当isValidating为false且isSubmitting为true时。

(2)Q:为什么在提交前Formik要“润色一下(touch)”表单中所有字段?
A:通常,当UI表单中输入字段被操作过后(Formik中称为“touched”)只显示与之相关的错误信息。于是,在提交一个表单前,Formik会touch一下所有字段,这样所有可能隐藏的错误都会变得可见。

(3)Q:如何避免两次重复提交?
A:办法是当isSubmitting为true时,禁止所有能够触发提交的调用

(4)Q:如何得知表单在提交前正在校验中?A:如果isValidating为true而且isSubmitting也为true的话,......

猜你在找的React相关文章