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

2023-05-16

一个基本的例子

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

{
   id: string,
   email: string,
   social: {
     facebook: string,
     twitter: string,
     // ...
   }
}

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

// User.js
import React from 'react';
import Dialog from 'MySuperDialog';
import { Formik } from 'formik';

const EditUserDialog = ({ user, updateUser, onClose }) => {
  return (
    <Dialog onClose={onClose}>
      <h1>Edit User</h1>
      <Formik
        initialValues={user /** { email, social } */}
        onSubmit={(values, actions) => {
          CallMyApi(user.id, values).then(
            updatedUser => {
              actions.setSubmitting(false);
              updateUser(updatedUser), onClose();
            },
            error => {
              actions.setSubmitting(false);
              actions.setErrors(transformMyAPIErrorToAnObject(error));
            }
          );
        }}
        render={({
          values,
          errors,
          touched,
          handleBlur,
          handleChange,
          handleSubmit,
          isSubmitting,
        }) => (
          <form onSubmit={handleSubmit}>
            <input
              type="email"
              name="email"
              onChange={handleChange}
              onBlur={handleBlur}
              value={values.email}
            />
            {errors.email && touched.email && <div>{errors.email}</div>}
            <input
              type="text"
              name="social.facebook"
              onChange={handleChange}
              onBlur={handleBlur}
              value={values.social.facebook}
            />
            {errors.social &&
              errors.social.facebook &&
              touched.facebook && <div>{errors.social.facebook}</div>}
            <input
              type="text"
              name="social.twitter"
              onChange={handleChange}
              onBlur={handleBlur}
              value={values.social.twitter}
            />
            {errors.social &&
              errors.social.twitter &&
              touched.twitter && <div>{errors.social.twitter}</div>}
            <button type="submit" disabled={isSubmitting}>
              Submit
            </button>
          </form>
        )}
      />
    </Dialog>
  );
};

简化编码

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


  • <Field>

  • <Form />


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

// EditUserDialog.js
import React from 'react';
import Dialog from 'MySuperDialog';
import { Formik, Field, Form } from 'formik';

const EditUserDialog = ({ user, updateUser, onClose }) => {
  return (
    <Dialog onClose={onClose}>
      <h1>Edit User</h1>
      <Formik
        initialValues={user /** { email, social } */}
        onSubmit={(values, actions) => {
          CallMyApi(user.id, values).then(
            updatedUser => {
              actions.setSubmitting(false);
              updateUser(updatedUser), onClose();
            },
            error => {
              actions.setSubmitting(false);
              actions.setErrors(transformMyAPIErrorToAnObject(error));
            }
          );
        }}
        render={({ errors, touched, isSubmitting }) => (
          <Form>
            <Field type="email" name="email" />
            {errors.email && touched.social.email && <div>{errors.email}</div>}
            <Field type="text" name="social.facebook" />
            {errors.social.facebook &&
              touched.social.facebook && <div>{errors.social.facebook}</div>}
            <Field type="text" name="social.twitter" />
            {errors.social.twitter &&
              touched.social.twitter && <div>{errors.social.twitter}</div>}
            <button type="submit" disabled={isSubmitting}>
              Submit
            </button>
          </Form>
        )}
      />
    </Dialog>
  );
};

React Native开发问题


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

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

// Formik +React Native示例
import React from 'react';
import { Button, TextInput, View } from 'react-native';
import { withFormik } from 'formik';

const enhancer = withFormik({
  /*...*/
});

const MyReactNativeForm = props => (
  <View>
    <TextInput
      onChangeText={props.handleChange('email')}
      onBlur={props.handleBlur('email')}
      value={props.values.email}
    />
    <Button onPress={props.handleSubmit} title="Submit" />
  </View>
);

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的任何内容。

请参考下面的代码:

// FormikReactNativeTextInput.js
import * as React from 'react';
import { TextInput } from 'react-native';

export default class FormikReactNativeTextInput extends React.Component {
  handleChange = (value: string) => {
    // remember that onChangeText will be Formik's setFieldValue
    this.props.onChangeText(this.props.name, value);
  };

  render() {
    // we want to pass through all the props except for onChangeText
    const { onChangeText, ...otherProps } = this.props;
    return (
      <TextInput
        onChangeText={this.handleChange}
        {...otherProps} // IRL, you should be more explicit when using TS
      />
    );
  }
}

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

// MyReactNativeForm.js
import { View, Button } from 'react-native';
import TextInput from './FormikReactNativeTextInput';
import { Formik } from 'formik';

const MyReactNativeForm = props => (
  <View>
    <Formik
      onSubmit={(values, actions) => {
        setTimeout(() => {
          console.log(JSON.stringify(values, null, 2));
          actions.setSubmitting(false);
        }, 1000);
      }}
      render={props => (
        <View>
          <TextInput
            name="email"
            onChangeText={props.setFieldValue}
            value={props.values.email}
          />
          <Button title="submit" onPress={props.handleSubmit} />
        </View>
      )}
    />
  </View>
);

export default MyReactNativeForm;

使用TypeScript开发Formik表单

(一)TypeScript类型

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

Render props (<Formik /> and <Field />)
import * as React from 'react';
import { Formik, FormikProps, Form, Field, FieldProps } from 'formik';

interface MyFormValues {
  firstName: string;
}

export const MyApp: React.SFC<{} /* whatever */> = () => {
  return (
    <div>
      <h1>My Example</h1>
      <Formik
        initialValues={{ firstName: '' }}
        onSubmit={(values: MyFormValues) => alert(JSON.stringify(values))}
        render={(formikBag: FormikProps<MyFormValues>) => (
          <Form>
            <Field
              name="firstName"
              render={({ field, form }: FieldProps<MyFormValues>) => (
                <div>
                  <input type="text" {...field} placeholder="First Name" />
                  {form.touched.firstName &&
                    form.errors.firstName &&
                    form.errors.firstName}
                </div>
              )}
            />
          </Form>
        )}
      />
    </div>
  );
};

(二)使用withFormik()

import React from 'react';
import * as Yup from 'yup';
import { withFormik, FormikProps, FormikErrors, Form, Field } from 'formik';

// Shape of form values
interface FormValues {
  email: string;
  password: string;
}

interface OtherProps {
  message: string;
}

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

const InnerForm = (props: OtherProps & FormikProps<FormValues>) => {
  const { touched, errors, isSubmitting, message } = props;
  return (
    <Form>
      <h1>{message}</h1>
      <Field type="email" name="email" />
      {touched.email && errors.email && <div>{errors.email}</div>}

      <Field type="password" name="password" />
      {touched.password && errors.password && <div>{errors.password}</div>}

      <button type="submit" disabled={isSubmitting}>
        Submit
      </button>
    </Form>
  );
};

//MyForm接收的props的类型
interface MyFormProps {
  initialEmail?: string;
  message: string; // if this passed all the way through you might do this or make a union type
}

//使用withFormik高阶组件包装你的表单
const MyForm = withFormik<MyFormProps, FormValues>({
  // Transform outer props into form values
  mapPropsToValues: props => {
    return {
      email: props.initialEmail || '',
      password: '',
    };
  },

  //添加定制的校验函数(也有可能是异步的)
  validate: (values: FormValues) => {
    let errors: FormikErrors = {};
    if (!values.email) {
      errors.email = 'Required';
    } else if (!isValidEmail(values.email)) {
      errors.email = 'Invalid email address';
    }
    return errors;
  },

  handleSubmit: values => {
    // do submitting things
  },
})(InnerForm);

// 你可以在任何地方使用<MyForm />
const Basic = () => (
  <div>
    <h1>My App</h1>
    <p>This can be anywhere in your application</p>
    <MyForm message="Sign up" />
  </div>
);

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的话,......

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 一文揭秘饿了么跨端技术的演进、实践与落地

    本文会先带领大家一起简单回顾下跨端技术背景与演进历程与在这一波儿接着一波儿的跨端浪潮中的饿了么跨端现状 以及在这个背景下 相较于业界基于 React Vue 研发习惯出发的各种跨端方案 饿了么为什么会选择走另外一条路 这个过程中我们的一些思
  • React中使用if else 条件判断

    在react中用jsx渲染dom的时候经常会遇到if条件判断 然而在jsx中竟是不允许if条件判断的 以下有几种判断方式 可以根据自己的应用场景 挑选适合的 方案一 class HelloMessage extends React Comp
  • React(一):React的设计哲学 - 简单之美

    React 一 React的设计哲学 简单之美 React 二 React开发神器Webpack React 三 理解JSX和组件 React 四 虚拟DOM Diff算法解析 React 五 使用Flux搭建React应用程序架构 Rea
  • react 上传文件(多选)功能入的坑

    1 这里报错是因为onChange的this指向不对 解决方法在constructor中写 this onChange this onChange bind this 或者在绑定事件的时候写 onChange this onChange b
  • React class组件、react-hook函数组件分别实现五子棋

    react class类组件 react hook函数组件分别实现五子棋 前言 使用create react app脚手架简单搭建 不用安装其他依赖 纯 js css实现 这里就只是简单的说明目录结构和思路 具体的代码实现请转到 Githu
  • vue发展历史简介

    基本介绍 Vue 是一套用于构建用户界面的 渐进式框架 与其它大型框架不同的是 Vue 被设计为可以自底向上逐层应用 最初它不过是个人项目 时至今日 已成为全世界三大前端框架之一 github 上拥有 17 8万 Star 领先于 Reac
  • 通过 API 调用设置表单的初始值

    在我的 React 游戏中 我使用名为 Formik 的 React 库作为表单 在其中 您可以像这样设置表单的初始值
  • Formik + 是的:如何在安装时立即验证表单?

    我想在安装表单时显示字段错误 提交后不行 Yup const validation Yup object shape field Yup string required Required Formik
  • 如何在 Yup 异步验证中设置动态错误消息?

    我正在使用 Yup 在 Formik 中尝试异步验证 test 方法并需要设置我从 API 获得的错误消息 根据后端的某些情况 错误消息会有所不同 尝试了这里提到的一些解决方案 https github com jquense yup is
  • 自定义hooks

    自定义传参hooks 把大多数的通用代码 或者props 抽成一个hooks 用hooks实现上下文 就不用再一一传参了 实现原理 主要是通过createContext useContext 生产 消费者模式 用于大量props一层一层传参
  • React Jsx转换成真实DOM过程?

    面试官 说说React Jsx转换成真实DOM过程 一 是什么 react 通过将组件编写的 JSX 映射到屏幕 以及组件中的状态发生了变化之后 React 会将这些 变化 更新到屏幕上 在前面文章了解中 JSX 通过 babel 最终转化
  • React Jsx转换成真实DOM过程?

    面试官 说说React Jsx转换成真实DOM过程 一 是什么 react 通过将组件编写的 JSX 映射到屏幕 以及组件中的状态发生了变化之后 React 会将这些 变化 更新到屏幕上 在前面文章了解中 JSX 通过 babel 最终转化
  • Typescript 错误和展开运算符是由 ...props 引起的?

    您好 当我尝试将扩展运算符与 Typescript 一起使用时 我遇到了问题 我的功能是useFormikContext and useField 函数来自 Formik 的库 当我添加 ts ignore一切都按预期工作得很好 但是 如果
  • 为什么 OnChange 在 Formik 中使用时不起作用?

    我正在尝试在 React 中使用 Formik 作为一个虚拟应用程序 如果我给出值作为道具 我将无法在任何一个输入框中输入任何内容 另一方面 如果我跳过 value 属性 那么我可以在框中键入内容 但提交时不会反映为值 这是代码 expor
  • 如何防止用户在反应中多次点击登录表单的提交按钮错误?

    我使用formik和react router dom进行react登录管理 但如果短时间内多次点击提交按钮 重定向到首页后会出现以下错误 警告 无法对已卸载的组件执行 React 状态更新 这是一个无操作 但它表明应用程序中存在内存泄漏 要
  • 是的,嵌套模式验证

    我正在尝试根据用户选择的选择选项有条件地验证对象 问题是我正在渲染货币列表 并且在尝试将其设为必填字段时遇到巨大困难 因为我必须传入一个空的对象开始 我的代码堆栈是 React Formik 和 Yup 所有最新版本 对象模式 catego
  • 如何在 Formik 上实现自定义 handleChange 函数?

    在输入元素中 handleChange 函数将从 onChange 事件接收事件对象 如何为如下非输入字段创建自定义 handleChange 函数 import React from react import useFormik from
  • 使用自定义组件时,onChange 处理程序不会触发

    我在用着Formik https jaredpalmer com formik用于在 React 应用程序中进行验证 验证工作正常 但我的 onChange 处理程序未触发
  • 表单验证后 isValid 保持 false

    我有一个自定义验证函数 但即使它没有返回错误 表单仍然无效 我将以下属性传递给 Formik validate import files gt return import files values length 0 import files
  • 是的/Formik 异步验证与去抖

    如何将去抖应用于下面的异步验证 代码来自 Yup 的 github https github com jquense yup mixedtestname string message string function test functio

随机推荐