61

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

 4 years ago
source link: https://www.tuicool.com/articles/qeuQJb7
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

jM7Vryi.png!web

代码地址: 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 (
<divclassName="container">
<divclassName="row mb-5">
<divclassName="col-lg-12 text-center">
<h1className="mt-5">Login Form</h1>
</div>
</div>
<divclassName="row">
<divclassName="col-lg-12">
<formonSubmit={this.handleSubmit}>
<divclassName="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}
/>
<divclassName="invalid-feedback">{formErrors.email}</div>
</div>
<divclassName="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}
/>
<divclassName="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}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:

I77zEjQ.png!web

代码地址: 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>
<divclassName="form-group">
<labelhtmlFor="email">Email</label>
<Field
type="email"
name="email"
className="form-control"
/>
</div>
<divclassName="form-group">
<labelhtmlFor="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. 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 <3) {
errors.password= "Password must be 3 characters at minimum";
}
returnerrors;
}}

// Vanilla React validation code. TakevaluesgivenbyhandleChange
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
});
};

验证准备好之后,你现在需要输出错误信息了。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}
/>
<divclassName="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 属性是如何从 组件中移除的:

VrABrib.png!web

代码地址: 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 (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 绝对是你所必备的开源库之一。它真的会加速你的开发流程,并且通过组件来抽象化你的表单以减少模板代码,比如 <Field /><Form />

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

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK