构建 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 (



Login Form

{formErrors.email}
{formErrors.password}
); }

现在我们需要写 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}isrequiredandcannot be empty`;
if(validity[name]) {
if(isEmail) {
validity[name] = emailTest.test(value);
fieldValidationErrors[name] = validity[name]
? ""
: `${name} should be avalidemail address`;
}
if(isPassword) {
validity[name] =value.length >=3;
fieldValidationErrors[name] = validity[name]
? ""
: `${name} should be3characters 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 (letkeyinformValues) {
lettarget = {
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 的能量,你可以把你的表单包裹在 组件中:

复制代码


{/* the rest of the code here */}


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

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

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

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

组件:

复制代码

 {
alert("Form is validated! Submitting the form...");
setSubmitting(false);
}}
>
{() => (
Email Password
)}

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

验证以及错误信息

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

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

复制代码

// Formik validation code. TakevaluesfromFormik
validate={values=> {
let errors = {};
if(values.email === "") {
errors.email = "Email is required";
}elseif(!emailTest.test(values.email)) {
errors.email = "Invalid email address format";
}
if(values.password=== "") {
errors.password= "Password is required";
}elseif(values.password.length  {
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}isrequiredandcannot be empty`;
if(validity[name]) {
if(isEmail) {
validity[name] = emailTest.test(value);
fieldValidationErrors[name] = validity[name]
? ""
: `${name} should be avalidemail address`;
}
if(isPassword) {
validity[name] =value.length >=3;
fieldValidationErrors[name] = validity[name]
? ""
: `${name} should be3characters minimum`;
}
}
this.setState({
formErrors: fieldValidationErrors,
formValidity: validity
});
};

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

复制代码

// Formik error message output



// Vanilla React error message output

{formErrors.email}

错误信息相关的代码其实是一样的,不过相比于原生的 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 的

组件会自动地运行你的验证方法,并且会在任何错误发生时取消提交的流程。常规的

元素需要你引入一个 onSubmit 属性,而 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 (letkeyinformValues) {
lettarget = {
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 绝对是你所必备的开源库之一。它真的会加速你的开发流程,并且通过组件来抽象化你的表单以减少模板代码,比如

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

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

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

About The Author

php