低代码到底是不是行业毒瘤?一线大厂怎么做的?戳此了解>>> 了解详情
写点什么

构建 React 表单必备的开源库:Formik

2019 年 7 月 24 日

构建React表单必备的开源库:Formik

使用 React 构建表单需要创建 state 作为用户数据的容器,并创建 props 作为控制 state 如何根据用户输入进行更新的方法。验证可以在用户的输入间隔间完成,表单提交时会执行任意一个提交函数。


这里是一个基础的 React 表单的例子,没有使用其他的库,只用了最精简的 Bootstrap 样式:



代码地址:https://codesandbox.io/s/q9r66xl44


在下面的例子中,我们首先在 constructor 方法中初始化了必要的 state 值。因为这里我们需要两个必要的 input, 即 email 和 password, 所以我们为它们的 input 值、正确性以及错误分别初始化了相应的 state:


constructor(props) {  super(props);  this.state = {    formValues: {      email: "",      password: ""    },    formErrors: {      email: "",      password: ""    },    formValidity: {      email: false,      password: false    },    isSubmitting: false  };}
复制代码


接下来,我们为表单创建 render 方法,其中 input 的值是从 state 中获取的:


render() {  const { formValues, formErrors, isSubmitting } = this.state;  return (    <div className="container">      <div className="row mb-5">        <div className="col-lg-12 text-center">          <h1 className="mt-5">Login Form</h1>        </div>      </div>      <div className="row">        <div className="col-lg-12">          <form onSubmit={this.handleSubmit}>            <div className="form-group">              <label>Email address</label>              <input                type="email"                name="email"                className={`form-control ${                  formErrors.email ? "is-invalid" : ""                }`}                placeholder="Enter email"                onChange={this.handleChange}                value={formValues.email}              />              <div className="invalid-feedback">{formErrors.email}</div>            </div>            <div className="form-group">              <label>Password</label>              <input                type="password"                name="password"                className={`form-control ${                  formErrors.password ? "is-invalid" : ""                }`}                placeholder="Password"                onChange={this.handleChange}                value={formValues.password}              />              <div className="invalid-feedback">{formErrors.password}</div>            </div>            <button              type="submit"              className="btn btn-primary btn-block"              disabled={isSubmitting}            >              {isSubmitting ? "Please wait..." : "Submit"}            </button>          </form>        </div>      </div>    </div>  );}
复制代码


现在我们需要写 handleChange 方法,用来根据用户的输入更新 state:


handleChange = ({ target }) => {  const { formValues } = this.state;  formValues[target.name] = target.value;  this.setState({ formValues });  this.handleValidation(target);};
复制代码


每当 state 的值有更新,我们会根据用户的输入执行验证方法。下面就是我们的 handleValidation 方法:


handleValidation = target => {  const { name, value } = target;  const fieldValidationErrors = this.state.formErrors;  const validity = this.state.formValidity;  const isEmail = name === "email";  const isPassword = name === "password";  const emailTest = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i;  validity[name] = value.length > 0;  fieldValidationErrors[name] = validity[name]    ? ""    : `${name} is required and cannot be empty`;  if (validity[name]) {    if (isEmail) {      validity[name] = emailTest.test(value);      fieldValidationErrors[name] = validity[name]        ? ""        : `${name} should be a valid email address`;    }    if (isPassword) {      validity[name] = value.length >= 3;      fieldValidationErrors[name] = validity[name]        ? ""        : `${name} should be 3 characters minimum`;    }  }  this.setState({    formErrors: fieldValidationErrors,    formValidity: validity  });};
复制代码


这个基础表单的最后一步是提交流程所需要的 handleSubmit 方法。我们首先要检查 formValidity 中的值,如果其中有为 false 的值,我们会再运行一次验证方法,而不会在此时提交表单。


handleSubmit = event => {  event.preventDefault();  this.setState({ isSubmitting: true });  const { formValues, formValidity } = this.state;  if (Object.values(formValidity).every(Boolean)) {    alert("Form is validated! Submitting the form...");    this.setState({ isSubmitting: false });  } else {    for (let key in formValues) {      let target = {        name: key,        value: formValues[key]      };      this.handleValidation(target);    }    this.setState({ isSubmitting: false });  }};
复制代码


现在这个表单已经可以使用了。React 只为你的应用提供 view 层,这意味这它只为制作表单组件提供了最基础的必需品。component、state 和 props 就像一块块拼图,你必须将他们拼起来才能构建一个可用的表单。


正如你所看到的,一个只有两个字符输入框的表单也需要这么多代码。试想一下,如果是一个拥有十个或者更多输入的表单,你将需要维护多少 state 的值。难以想象!


是的,使用 React 制作表单并不好玩;它是非常繁琐和死板的。构建表单并创建验证方法是非常无趣的任务。在每一个表单中,你至少要做到以下这几点:


  1. 为表单的值、错误以及正确性创建相应的 state

  2. 处理用户的输入并更新 state

  3. 创建验证函数

  4. 处理提交


用原生的 React 方式去创建表单,你需要编写从构建 state 到表单提交的过程中的每一部分。我完成过难以计数的 React 表单,每一次我都会觉得构建表单的这一部分特别得无趣而且耗时。幸运的是,并不是只有我这么觉得。


初探 Formik

Jared Palmer 出于对构建 React 表单的沮丧编写了 Formik 库。他需要一种对 input 组件以及表单提交流程进行标准化的方式。Formik 会帮助你编写创建表单过程中三个最恼人的部分:


  1. 读取或者写入表单的 state 的值

  2. 验证以及错误信息

  3. 处理表单提交


下面是同样的表单,不过这一次使用了 Formik:



代码地址:https://codesandbox.io/s/w63wr2xqq7


这一个新的表单只使用了 Formik 库中的四个额外组件:<Formik /><Form /><Field />以及<ErrorMessage />。为了释放 Formik 的能量,你可以把你的表单包裹在<Formik />组件中:


<Formik>  <Form>    {/* the rest of the code here */}  </Form></Formik>
复制代码


接下来让我们看看相对于 React 原生的方式,Formik 是如何把构建表单变得容易的。


读取或者写入表单的 state 的值

Formik 会通过它的 initialValues 属性在内部为用户的输入创建相应的 state,所以你不需要自己在 constructor 方法中初始化 state。


为了能够读取或者写入 Formik 的内部 state,你可以使用<Field />组件来代替常规的 HTML <input />组件。这个组件会神奇地将 Formik 的 state 和 input 的值进行同步,因此你不需要将 value 和 onChange 属性传递给组件:


<Formik  initialValues={{ email: "", password: "" }}  onSubmit={({ setSubmitting }) => {    alert("Form is validated! Submitting the form...");    setSubmitting(false);  }}>  {() => (    <Form>      <div className="form-group">        <label htmlFor="email">Email</label>        <Field          type="email"          name="email"          className="form-control"        />      </div>      <div className="form-group">        <label htmlFor="password">Password</label>        <Field          type="password"          name="password"          className="form-control"        />      </div>    </Form>  )}</Formik>
复制代码


使用 Formik,你没有必要在 constructor 方法中初始化 state,也不需要创建自己的 handleChange 方法了。这些都被 Formik 接管了。


验证以及错误信息

Formik 中的验证是在某些特定事件发生时自动执行的。所有常见的事件,包括用户输入、焦点变化以及提交,你都不需要关心。你所需要做得只是给 Formik 的 validate 属性传一个函数。


下面对比一下 Formik 的验证和原生 React 的验证:


// Formik validation code. Take values from Formikvalidate={values => {  let errors = {};  if (values.email === "") {    errors.email = "Email is required";  } else if (!emailTest.test(values.email)) {    errors.email = "Invalid email address format";  }  if (values.password === "") {    errors.password = "Password is required";  } else if (values.password.length < 3) {    errors.password = "Password must be 3 characters at minimum";  }  return errors;}}
// Vanilla React validation code. Take values given by handleChangehandleValidation = target => { const { name, value } = target; const fieldValidationErrors = this.state.formErrors; const validity = this.state.formValidity; const isEmail = name === "email"; const isPassword = name === "password"; const emailTest = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i; validity[name] = value.length > 0; fieldValidationErrors[name] = validity[name] ? "" : `${name} is required and cannot be empty`; if (validity[name]) { if (isEmail) { validity[name] = emailTest.test(value); fieldValidationErrors[name] = validity[name] ? "" : `${name} should be a valid email address`; } if (isPassword) { validity[name] = value.length >= 3; fieldValidationErrors[name] = validity[name] ? "" : `${name} should be 3 characters minimum`; } } this.setState({ formErrors: fieldValidationErrors, formValidity: validity });};
复制代码


验证准备好之后,你现在需要输出错误信息了。Formik 的<ErrorMessage />组件会自动地为包含 name 属性的<Field />组件展示错误信息。你可以通过 component 属性来调整展示什么样的 HTML 标签。因为这个例子中的表单使用了 Bootstrap 的样式,所以你同时也需要添加一个 className 属性:


// Formik error message output<Field  type="email"  name="email"  className={`form-control ${    touched.email && errors.email ? "is-invalid" : ""  }`}/><ErrorMessage  component="div"  name="email"  className="invalid-feedback"/>
// Vanilla React error message output<input type="email" name="email" className={`form-control ${ formErrors.email ? "is-invalid" : "" }`} placeholder="Enter email" onChange={this.handleChange} value={formValues.email}/><div className="invalid-feedback">{formErrors.email}</div>
复制代码


错误信息相关的代码其实是一样的,不过相比于原生的 React,Formik 的验证少了很多的代码。Formik,出发!


Yup 让验证更简单

尽管你已经能够感受到在验证流程中使用 Formik 所带来的益处,其实你还可以通过使用对象模式验证器让这个流程变得更简单。


对象模式验证器是一个可以让你定义某个 JavaScript 对象的蓝图,并确保在整个验证流程中该对象的值都和这个蓝图相匹配。这在验证表单数据时特别有用,因为它其实就是 Formilk 的 values 属性保存的一个对象。


目前有一个这样的库叫做 Yup,Formik 的作者非常喜欢 Yup,于是他引入了一个连接 Yup 和 Formik 的特殊属性,叫做 validationScheme。这个属性会自动的把 Yup 的验证错误转化成一个友好的对象,该对象的键值匹配了 values 和 touched。


下面是一个 Formik 使用 Yup 作为其验证模式的例子。注意看 validate 属性是如何从组件中移除的:



代码地址:https://codesandbox.io/s/olql6q2m1q


通过使用 Yup 的对象模式验证器,你再也不需要手动的去写 if 条件判断。你可以访问这个Github目录来更多得了解 Yup 以及它能够做哪些类型的验证。


表单提交流程

Formik 的<Form />组件会自动地运行你的验证方法,并且会在任何错误发生时取消提交的流程。常规的<form />元素需要你引入一个 onSubmit 属性,而 Formik 的<Form />封装会运行你传递给<Formik />组件的 onSubmit 属性的函数:


// Formik's submit code. Won't be executed if there are any errors.onSubmit={({ setSubmitting }) => {  alert("Form is validated!");  setSubmitting(false);}}
// Vanilla React submit code. Check on validity state then run validation manually.handleSubmit = event => { event.preventDefault(); this.setState({ isSubmitting: true }); const { formValues, formValidity } = this.state; if (Object.values(formValidity).every(Boolean)) { alert("Form is validated!"); this.setState({ isSubmitting: false }); } else { for (let key in formValues) { let target = { name: key, value: formValues[key] }; this.handleValidation(target); } this.setState({ isSubmitting: false }); }};
复制代码


Formik 最少只需要 4 行代码来完成提交,而且你不需要追踪表单输入的正确性。这真的是很简洁!


那么 redux-form 怎么样?

当然,redux-form很好用,但是首先你需要使用 Redux。如果你要用 MobX 呢?如果之后有一个更新,更好的库出现而你想用它来替代 Redux 呢?在这样的前提下,你的 React 表单会不会在某种程度上影响到你整个应用的数据流?


思考一下:用户名文本输入框的值对于全局的应用来说是不是有用?如果不是,那么使用 Redux 来追踪它的值就显得没有必要了。连布道者Dan Abramov也说过一样的话。


另一个关于 redux-form 的问题是你把表单的 input 值都保存在 Redux 的 store 里。这意味着你的应用会在每次按键更新文本框值的时候去调用 Redux 的 reducer。这并不是一个好主意。


我喜欢用 Formik 来编写表单,但是如果你更喜欢 redux-form,那也是可以的。


总结

构建表单并不是 React 所擅长的事。幸运的是,React 拥有一个开发者社区,这些开发者愿意帮助他人并将编写代码的流程变得更加简单。


如果你需要在你的 React 应用中编写很多表单, 那么 Formik 绝对是你所必备的开源库之一。它真的会加速你的开发流程,并且通过组件来抽象化你的表单以减少模板代码,比如<Field /><Form />


一个原生的 React 表单需要你确定你自己的 state 值和方法,而你可以简单的通过把属性传递给<Formik />组件来做同样的事情:处理用户输入、验证输入以及表单提交。


如果你想更多地了解 Formik, 你可以在这里看作者自己写的文档。谢谢阅读!


英文原文: https://blog.logrocket.com/building-better-react-forms-with-formik/


2019 年 7 月 24 日 18:172989

评论

发布
暂无评论
发现更多内容

互联网大厂根本没有题库!了解这些却能让你掌握“隐形题库”

互联网架构师小马

程序员 面试 面试题 Java 面试 找工作

对CAP的理解

朱月俊

Rust所有权,可转可借

袁承兴

rust 指针 函数调用 引用 内存管理

分布式系统架构作业

qihuajun

你要的《Spring系列源码解读》PDF它来了

z小赵

Java spring

CAP原理

chenzt

现在微服务这么火,你还不了解吗?阿里P8推荐的微服务学习指南

互联网架构师小马

Docker 微服务 Spring Cloud Spring Boot dubbo

第6周作业

andy

架构师训练营第六周作业

R20114

极客大学架构师训练营

第6周课后练习-请简述CAP原理

Dawn

极客大学架构师训练营

java 后端博客系统文章系统——No5

猿灯塔

Java

第6周总结

andy

架构师训练营第六周作业

一剑

区块链扩张路径变局:从技术比拼转向生态落地

CECBC区块链专委会

记一次Apache的代码导致生产问题

java金融

Java Apache spring BeanUtils

没错,用三方 Github 做授权登录就是这么简单!(OAuth2.0实战)

程序员内点事

Java GitHub oauth2.0

HashMap学习总结

大刘

hashmap hash

2020-07-11-第六周作业

路易斯李李李

第六周总结

秦宝齐

作业

1. react起始 | 2020年前端再入门系列连载

chaozh

前端开发 React

CAP Theorem

dongge

架构师训练营第六周总结

一剑

面向对象编程学习

一叶知秋

第六周作业

秦宝齐

学习 极客大学架构师训练营

React与前端开发发展史

pingan8787

LeetCode题解:15. 三数之和,JavaScript双循环+HashMap,详细注释

Lee Chen

LeetCode 前端进阶训练营

详解区块链应用市场与落地应用现状

CECBC区块链专委会

数据结构学习心得

程李文华

用Roslyn做个JIT的AOP

八苦-瞿昙

技术 随笔杂谈 aop 代理 框架

MySQL 连接查询超全详解

X先生

MySQL 数据库

架构师训练营第 6 周作业二

不谈

2021 ThoughtWorks 技术雷达峰会

2021 ThoughtWorks 技术雷达峰会

构建React表单必备的开源库:Formik-InfoQ