使用 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 制作表单并不好玩;它是非常繁琐和死板的。构建表单并创建验证方法是非常无趣的任务。在每一个表单中,你至少要做到以下这几点:
为表单的值、错误以及正确性创建相应的 state
处理用户的输入并更新 state
创建验证函数
处理提交
用原生的 React 方式去创建表单,你需要编写从构建 state 到表单提交的过程中的每一部分。我完成过难以计数的 React 表单,每一次我都会觉得构建表单的这一部分特别得无趣而且耗时。幸运的是,并不是只有我这么觉得。
初探 Formik
Jared Palmer 出于对构建 React 表单的沮丧编写了 Formik 库。他需要一种对 input 组件以及表单提交流程进行标准化的方式。Formik 会帮助你编写创建表单过程中三个最恼人的部分:
读取或者写入表单的 state 的值
验证以及错误信息
处理表单提交
下面是同样的表单,不过这一次使用了 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 Formik
validate={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 handleChange
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
});
};
复制代码
验证准备好之后,你现在需要输出错误信息了。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/
评论