使用Formik轻松开发更高质量的React表单一入门

前言

发现Formik是在我学习redux-form过程中从国外一篇博客上偶然发现的,看到作者的高度肯定后我立即转到github上,正如许多朋友所关注的,Formik的星数达8282,这个数字在github虽然不算很高,但是从基于React技术跨平台表单开发这个主题角度来看,此数字已经相当可观了。不自觉地,我对比了redux-form与Formik的几个数据,如下:

开源库的时间 星数
redux-form 3年以前 10210
Formik 1年前 8282

于是,我得出如下几个不太肯定的结论(欢迎有兴趣的朋友一起讨论):

1,redux-form是目前基于React+Redux开发构建表单子项目时主要开源选择方案;
2,redux-form很可能是目前大多数React程序员心目中高效解决方案的选择;
3,在github上Formik在未来一年后星数很有可能会超过redux-form项目。

我作出上述猜测主要理由是,经过这段时间我redux-form学习,我发现要深度掌握redux-form实践应用并灵活地处理各种有关问题,需要花费相当的代价。尤其说明问题的是,在表单体积膨胀和数量急剧增加的情况下,系统的性能有可能受到严重的影响。现在我有了react-redux基础,并理解了redux-form应用原理后,感觉使用Formik开发React表单一下变得异常轻松愉快!

为了节约时间,本系列的几篇我会首先使用英语方式,再在后面的时间里逐篇翻译成中文

为什么不直接用Redux-Form?

至此,你可能会想:“为什么不使用Redux-Form这一方案呢?”对于这个问题,包括我在内的Redux用户都会自然地提出这个问题。对此,Formik开发者的列出如下三个理由:

**1. React开发专家Dan Abramov认为,表单状态本质上是短暂的和局部性的,因此通过Redux解决方案(或任何类型的Flux库)来跟踪表单状态是不必要的。

  1. Redux-Form往往针对每一次用户击键多次调用前端系统顶层的Redux reducer。对于小应用来说,这还算可以;但是,随着你的Redux应用程序的不断增长,输入要求很可能会急剧膨胀——如果你使用Redux-Form作为前端表单解决方案的话。
  2. Redux-Form经压缩打包后的大小为22.5 kB,而Formik的大小为7.8 kB。**

Formik的开发目标是:使用最小的易于使用的API调用创建一个可伸缩的、持久性的表单生成器。当然,还提供另外一些功能供开发者定制使用。

灵感来源

Formik受到Brent Jackson开发的高阶组件的启发 。同时还吸收了Redux-Form库的命名方案;还有受到React-Motion和React-Router 4的启发最新引入的render属性。无论你是否使用过上述这些库,Formik 都会使你在短短的数分钟内快速入门。

安装

把Formik添加到你的已有项目中的方式如下:

npm i formik --save

示例

官方网站上提供了一组类似于redux-form官方的示例供初学者学习之用。它们有:

  • 基本类型示例
  • 同步校验示例
  • 开发自己的输入原型
  • 与react-select联合应用
  • 与Draft.js联合应用
  • 访问React生命周期函数
  • 在React Native开发中的应用

Formik的核心

Formik跟踪表单状态,然后以props方式把此状态还有一些可重用的方法和事件处理器(例如 handleChange,handleBlur和handleSubmit暴露给表单组件。其中,handleChange 和handleBlur工作方式完全一样——都使用name或者id属性标记要更新的表单字段。
归纳来看,可以使用如下两种方式之一来使用Formik:

上述两种方式完全一样工作,内部实现原理完全相同。只是各自在使用风格上有所不同而已。请参考如下代码

//高阶组件方式
import React from 'react';
import { withFormik } from 'formik';

// Our inner form component which receives our form's state and updater methods as props
const InnerForm = ({
  values,errors,touched,handleChange,handleBlur,handleSubmit,isSubmitting,}) => (
  <form onSubmit={handleSubmit}>
    <input
      type="email"
      name="email"
      onChange={handleChange}
      onBlur={handleBlur}
      value={values.email}
    />
    {touched.email && errors.email && <div>{errors.email}</div>}
    <input
      type="password"
      name="password"
      onChange={handleChange}
      onBlur={handleBlur}
      value={values.password}
    />
    {touched.password && errors.password && <div>{errors.password}</div>}
    <button type="submit" disabled={isSubmitting}>
      Submit
    </button>
  </form>
);

//使用withFormik HoC包装表单
const MyForm = withFormik({
  // Transform outer props into form values
  mapPropsTovalues: props => ({ email: '',password: '' }),// Add a custom validation function (this can be async too!)
  validate: (values,props) => {
    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;
  },//表单提交处理器
  handleSubmit: (
    values,{
      props,setSubmitting,setErrors /* setValues,setStatus,and other goodies */,}
  ) => {
    LoginToMyApp(values).then(
      user => {
        setSubmitting(false);
        // do whatevs...
        // props.updateUser(user)
      },errors => {
        setSubmitting(false);
        // Maybe even transform your API's errors into the same shape as Formik's!
        setErrors(transformMyApiErrors(errors));
      }
    );
  },})(InnerForm);

//然后你可以在任何地方自由使用<MyForm />组件
const Basic = () => (
  <div>
    <h1>My Form</h1>
    <p>This can be anywhere in your application</p>
    <MyForm />
  </div>
);

export default Basic;
// Render Prop
import React from 'react';
import { Formik } from 'formik';

const Basic = () => (
  <div>
    <h1>My Form</h1>
    <p>This can be anywhere in your application</p>
    {/*
          The benefit of the render prop approach is that you have full access to React's
          state,props,and composition model. Thus there is no need to map outer props
          to values...you can just set the initial values,and if they depend on props / state
          then--boom--you can directly access to props / state.
          The render prop accepts your inner form component,which you can define separately or inline
          totally up to you:
          - `<Formik render={props => <form>...</form>}>`
          - `<Formik component={InnerForm}>`
          - `<Formik>{props => <form>...</form>}</Formik>` (identical to as render,just written differently)
        */}
    <Formik
      initialValues={{
        email: '',password: '',}}
      validate={values => {
        // same as above,but feel free to move this into a class method Now.
        let 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;
      }}
      onSubmit={(
        values,{ setSubmitting,setErrors /* setValues and other goodies */ }
      ) => {
        LoginToMyApp(values).then(
          user => {
            setSubmitting(false);
            // do whatevs...
            // props.updateUser(user)
          },errors => {
            setSubmitting(false);
            // Maybe transform your API's errors into the same shape as Formik's
            setErrors(transformMyApiErrors(errors));
          }
        );
      }}
      render={({
        values,}) => (
        <form onSubmit={handleSubmit}>
          <input
            type="email"
            name="email"
            onChange={handleChange}
            onBlur={handleBlur}
            value={values.email}
          />
          {touched.email && errors.email && <div>{errors.email}</div>}
          <input
            type="password"
            name="password"
            onChange={handleChange}
            onBlur={handleBlur}
            value={values.password}
          />
          {touched.password && errors.password && <div>{errors.password}</div>}
          <button type="submit" disabled={isSubmitting}>
            Submit
          </button>
        </form>
      )}
    />
  </div>
);

export default Basic;

补充说明

正你从上面观察到的,表单的校验逻辑完全由开发者自己实现。你可以心情使用第三方库来编写你自己的定制校验器。从官方网站上了解到,示例中大量使用Yup库来实现对象的格式校验。它提供了一种十分类似于Joi / React PropTypes的API,只不过在浏览器中尺寸十分小巧,且对运行时应用来说已经足够快。在此建议同学们也积极使用Yup。并且在Formik中也针对Yup提供了一种特别的配置选项/属性称作validationSchema,它能够把Yup的校验错误自动转换成一种很小的对象(对象中也提供了values和touched等键支持)。使用npm把Yup安装到你的项目中的方式如下:

npm install yup --save

参考资料

1.https://github.com/jaredpalmer/formik
2.http://www.lizhe.name/node/252
3.https://keyholesoftware.com/2017/10/23/the-joy-of-forms-with-react-and-formik/

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom