Skip to content
构建 React 表单必备的开源库:Formik
2013 年 4 月 25 日
使用 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 制作表单并不好玩;它是非常繁琐和死板的。构建表单并创建验证方法是非常无趣的任务。在每一个表单中,你至少要做到以下这几点:
- 为表单的值、错误以及正确性创建相应的 state
-
处理用户的输入并更新 state
-
创建验证函数
-
处理提交
用原生的 React 方式去创建表单,你需要编写从构建 state 到表单提交的过程中的每一部分。我完成过难以计数的 React 表单,每一次我都会觉得构建表单的这一部分特别得无趣而且耗时。幸运的是,并不是只有我这么觉得。
初探 Formik
Jared Palmer 出于对构建 React 表单的沮丧编写了 Formik 库。他需要一种对 input 组件以及表单提交流程进行标准化的方式。Formik 会帮助你编写创建表单过程中三个最恼人的部分:
- 读取或者写入表单的 state 的值
-
验证以及错误信息
-
处理表单提交
下面是同样的表单,不过这一次使用了 Formik:
代码地址: https://codesandbox.io/s/w63wr2xqq7
这一个新的表单只使用了 Formik 库中的四个额外组件:
,
,
以及
。为了释放 Formik 的能量,你可以把你的表单包裹在
组件中:
复制代码
接下来让我们看看相对于 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);
}}
>
{() => (
)}
使用 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