如何构建 React 表单组件
无论是登录页面还是内部工具,你的 React 应用都需要表单,而通过原始 HTML 输入处理事件和数据流可不是件容易的事。本指南将引导你了解如何使用该react-hook-form
库,并逐步指导你完成一个项目,在这个项目中,我们为一个内部工具创建了一个表单,并为其扩展了一些实用功能。
阅读完本文后,您将了解如何:
- 使用以下方式创建简单表单
react-hook-form
- 表单样式
- 验证您的表单
- 向表单添加错误
入门/基础知识
如果您只是来获取一些代码,我们可以帮您。
在本教程中,我们将使用一个列出并排序数据的表格,并使用一个漂亮的日期选择器来筛选订单。
我们知道大多数人都是在网上下单,但我们必须意识到,有时顾客更喜欢打电话订餐。这意味着我们需要赋予客服人员添加新订单的功能。
我们的 React 表单组件需要能够:
- 接受客户的姓名、地址、订单日期和订单号
- 验证客户支持代表输入的数据
- 向销售代表显示错误
最终产品的外观和感觉如下:
首先,react-hook-form
我们要构建一个库来处理表单数据,并完成所有复杂的验证、错误处理和提交工作。库中没有实体组件。我们将要构建的表单组件将仅使用标准jsx
标签。
首先,我们将构建一个没有任何样式的简单表单——它将包含一堆textarea
输入框,供客服代表填写客户姓名、地址、订单日期和订单号,最后是一个简单的“提交”按钮。请记住,我们使用了 React Hooks。Hooks 是 React 的一个相当新的功能,因此如果您不熟悉,我们建议您在开始本教程之前先react-hook-form
查看 React 的Hooks 概览文档。
导入useForm()
钩子后,需要执行以下基本步骤:
- 使用
useForm()
钩子获取register
和handleSubmit()
。
创建表单时,你需要传入register
prop,以便用户添加的值以及你的验证规则能够被提交。在本教程的后面,我们将使用它来处理验证。for函数将你的实际表单连接到(它首先提供了 register 方法)。ref
register
handleSubmit()
onSubmit
react-hook-form
const { register, handleSubmit } = useForm();
- 创建一个函数来处理你的数据,这样你的数据实际上就会进入你的数据库
你的后端是你自己写的,但我们假设saveData()
在另一个文件中有一个函数,用于将数据保存到数据库。这只是console.log(data)
为了本教程的目的。
- 渲染表单
我们正在创建一个 React 表单组件,因此我们将使用与表单相关的jsx
标签来构建它,例如<form>
、<h1>
、<label>
和<input>
让我们从一个<form>
容器开始。确保将你的saveData()
函数传入从钩子获取的react-hook-form
,然后传入标签中的。如果这听起来很令人困惑,请看一下下面的代码:handleSubmit()
useForm()
onSubmit()
<form>
<form onSubmit={handleSubmit(data => saveData(data))}>
...
</form>
接下来,让我们添加一个标题,<h1>
以便我们的代表知道此表单的用途:
<form ...>
<h1>New Order</h1>
</form>
我们将创建四个<label>
和 ,<input>
分别用于姓名、地址、日期和订单号。对于每个<input>
,请确保将register
其从useForm()
钩子传递到属性中ref
,并在 name 属性中为其命名。
<label>Name</label>
<input name="name" ref={register} />
<label>Address</label>
<input name="address" ref={register} />
<label>Date</label>
<input name="date" ref={register} />
<label>Order Number</label>
<input name="order" ref={register} />
<input>
最后,我们将使用“提交”类型添加一个提交按钮:
<input type="submit" />
综合起来,我们将得到以下内容:
import React from "react";
import { useForm } from "react-hook-form";
import saveData from "./some_other_file";
export default function Form() {
const { register, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(data => saveData(data))}>
<h1>New Order</h1>
<label>Name</label>
<input name="name" ref={register} />
<label>Address</label>
<input name="address" ref={register} />
<label>Date</label>
<input name="date" ref={register} />
<label>Order Number</label>
<input name="order" ref={register} />
<input type="submit" />
</form>
);
}
看起来是这样的:
太棒了,现在我们有了一个(有点)可以工作的表格。
使用 CSS 进行样式设置
您可以使用 CSS 模块、styled-components
或您喜欢的样式轻松设置表单样式。在本教程中,我们将使用styled-components
。
首先,我们安装并导入style-components
到项目中。然后,创建一个基于 的样式组件<div>
,并将所有漂亮的 CSS 代码放入其中。最后,我们将表单包裹在<Styles>
标签中以应用样式。很简单!
import React from "react";
import { useForm } from "react-hook-form";
import styled from "styled-components";
import saveData from "./some_other_file";
const Styles = styled.div`
background: lavender;
padding: 20px;
h1 {
border-bottom: 1px solid white;
color: #3d3d3d;
font-family: sans-serif;
font-size: 20px;
font-weight: 600;
line-height: 24px;
padding: 10px;
text-align: center;
}
form {
background: white;
border: 1px solid #dedede;
display: flex;
flex-direction: column;
justify-content: space-around;
margin: 0 auto;
max-width: 500px;
padding: 30px 50px;
}
input {
border: 1px solid #d9d9d9;
border-radius: 4px;
box-sizing: border-box;
padding: 10px;
width: 100%;
}
label {
color: #3d3d3d;
display: block;
font-family: sans-serif;
font-size: 14px;
font-weight: 500;
margin-bottom: 5px;
}
.error {
color: red;
font-family: sans-serif;
font-size: 12px;
height: 30px;
}
.submitButton {
background-color: #6976d9;
color: white;
font-family: sans-serif;
font-size: 14px;
margin: 20px 0px;
`;
function Form() {
const { register, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(data => saveData(data))}>
<label>Name</label>
<input name="name" ref={register} />
<label>Address</label>
<input name="address" ref={register} />
<label>Date</label>
<input name="date" ref={register} />
<label>Order Number</label>
<input name="order" ref={register} />
<input type="submit" className="submitButton" />
</form>
);
}
export default function App() {
return (
<Styles>
<Form />
</Styles>
);
}
这是很多样式代码,但看看它给我们带来了什么!
使用 React 组件库
如果您讨厌与 CSS 作斗争,那么使用 React 组件库或许是个不错的选择。它可以添加许多功能,例如动画,而这些功能实现起来非常耗时。如果您不熟悉众多的 React 组件库,可以查看我们最近介绍的几款我们最喜欢的组件库的文章。在本例中,我们将使用Material UI。
最简单的 React 组件库方法是使用一个将字段暴露ref
为 prop 的组件库。然后,你只需将其替换为字段<input>
,并传递register
给该 ref 即可。
import { Button, TextField } from "@material-ui/core";
...
function Form() {
const { register, handleSubmit } = useForm();
return (
<>
<h1>New Order</h1>
<form onSubmit={handleSubmit(data => saveData(data))}>
<label>Name</label>
<TextField name="name" inputRef={register} />
...
// Let's use Material UI's Button too
<Button variant="contained" color="primary">Submit</Button>
</form>
</>
);
}
现在,我们获得了 Material-UI 的流畅性和功能性。
验证你的 React 表单组件
我们最不希望看到的就是客服人员把错误的数据添加到我们的数据库中。如果我们有其他应用程序使用相同的数据,比如某个时间段内订单数量的报表,那么添加一个格式不正确的日期可能会毁了整个系统。
对于我们的用例,我们将以以下形式添加验证:
- 将所有字段设为必填项
- 添加地址验证器
- 生效日期
- 验证订单号
将所有字段设为必填项
要使字段成为必填字段,您只需将一个对象传递到register()
输入中的 prop 中即可{required: true}
。
<input name="name" ref={register({ required: true })} />
这将标记errors
“名称”字段的道具,然后可以使用它来添加错误消息(参见下一部分)。
添加地址验证器
为了让我们的生活更轻松,我们将添加一个验证器来检查用户输入的地址是否存在并且格式是否正确。我们将使用示例中的模拟函数并向您展示如何将其集成到 React 表单组件中。
首先,我们定义验证器函数。就我们的目的而言,我们只是检查一个特定的字符串。在这里,你需要连接到你的验证器库。
function addressValidator(address) {
if (address === "123 1st St., New York, NY") {
return true;
}
return false;
}
接下来,我们为收银机添加地址输入验证。确保传递用户输入的“值”。如果验证函数返回 true,则表示验证通过,不会出现任何错误。
<input name="address" ref={register({
required: true,
validate: value => addressValidator(value),
})} />
如果您想进一步进行地址验证,而不仅仅是添加模拟函数(您可能会这样做,因为这在生产中是无用的),我们建议您查看此处有关验证位置数据的精彩教程。
生效日期
为了确保用户仅在我们的日期输入字段中输入有效日期,我们将type="date"
在 React 表单组件中添加日期输入字段,以强制用户以我们指定的格式填写字段。
在某些浏览器(例如 Chrome)中,这会在输入框中添加一个 DatePicker。在所有浏览器中,它都会为客服代表输入的日期提供清晰的格式,并且不允许他们使用其他格式。我们甚至可以添加一个最大日期,以防止客服代表意外添加未来的订单日期(尽管我们都希望跳过 2020 年)。
对于本节,我们将使用该moment
库,因为它使日期格式化比 JavaScript 的原生日期容易得多。
import moment from 'moment';
...
<input
name="date"
type="date"
max={moment().format("YYYY-MM-DD")}
ref={register({ required: true })}
/>
与寄存器相比,在输入中验证日期的妙处在于,我们不必浪费时间和精力构建错误消息,因为输入将阻止用户输入错误的值。
看起来不错!
验证订单号
对于我们的订单号字段,我们需要添加验证以确保输入在我们的系统中是有效的订单号。react-hook-form
通过将“模式”传递到寄存器中,有一种非常简单的方法来应用正则表达式验证。
假设我们的订单号始终为 14 个整数(尽管这个正则表达式可以轻松更新以适合您的订单号)。
<input
name="order"
ref={register({
required: true,
minLength: 14,
maxLength: 14,
pattern: /\d{14}/,
})}
/>
太棒了!现在,当订单号不符合我们指定的模式时,就会弹出错误提示。更多详情,请参阅文档register
部分react-hook-form
。
在 React 表单组件中传达错误
使用 ,为表单添加错误处理非常简单react-hook-form
。让我们先来告知某些字段是必填项。我们要做的就是errors
从useForm()
钩子中获取信息,然后添加一个条件语句,以便在需要时将它们渲染到输入框下方。
function Form() {
const { register, errors, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(data => saveData(data))}>
<h1>New Order</h1>
<label>Name</label>
<input name="name" ref={register({ required: true })} />
{errors.name && "Required"}
<label>Address</label>
<input
name="address"
ref={register({
required: true,
validate: value => addressValidator(value)
})}
/>
{errors.address && "Required"}
<label>Date</label>
<input
name="date"
type="date"
max={moment().format("YYYY-MM-DD")}
ref={register({ required: true })}
/>
{errors.date && "Required"}
<label>Order Number</label>
<input
name="order"
ref={register({
required: true,
pattern: /\d{14}/,
})}
/>
{errors.order && "Required"}
<input type="submit" />
</form>
);
}
errors.name
注意,我们如何使用和 来引用特定输入字段的错误errors.date
。我们的错误如下所示:
最后一个问题——由于这些错误是条件性的,它们会增加表单的大小。为了解决这个问题,我们将创建一个简单的错误组件,即使没有文本,它也会渲染错误的高度。我们还会将文本颜色设为红色,以便更容易看到。
import React from "react";
import { useForm } from "react-hook-form";
import styled from "styled-components";
import saveData from "./some_other_file";
const Styles = styled.div`
background: lavender;
...
.error {
color: red;
font-family: sans-serif;
font-size: 12px;
height: 30px;
}
`;
// Render " " if no errors, or error message if errors
export function Error({ errors }) {
return <div className={"error"}>{errors ? errors.message : " "}</div>;
}
export function Form() {
const { register, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(data => saveData(data))}>
<h1>New Order</h1>
<label>Name</label>
<input name="name" ref={register({ required: true })} />
<Error errors={errors.name} />
<label>Address</label>
<input
name="address"
ref={register({
required: true,
validate: value => addressValidator(value)
})}
/>
<Error errors={errors.address} />
<label>Date</label>
<input
name="date"
type="date"
max={moment().format("YYYY-MM-DD")}
ref={register({ required: true })}
/>
<Error errors={errors.date} />
<label>Order Number</label>
<input
name="order"
ref={register({
required: true,
pattern: /\d{14}/,
})}
/>
<Error errors={errors.order} />
<input type="submit" className="submitButton" />
</form>
);
}
...
但是等等!没有要渲染的错误消息文本。为了解决这个问题,让我们从必需验证开始。我们通过为特定类型的错误添加错误消息来实现。
<input name="name" ref={register({ required: 'Required' })} />
检查你的代码,并在所有看到的地方都改成required: true
这样required: 'Required'
。现在它的功能更像我们在现实世界中期望看到的表单了:
稍等!我们验证的不仅仅是必填字段。让我们更详细地分析一下这些错误,以便我们的客户支持代表知道如何解决问题。
添加地址错误
要向您的validate
部分添加地址错误,只需添加一个,||
这样如果您的验证函数返回“false”,它将显示您的消息。
<input
name="address"
ref={register({
required: 'Required',
validate: value => addressValidator(value) || 'Invalid address',
})}
/>
您的错误看起来如下:
添加订单号错误
在我们的系统中,订单号始终为 14 位数字,由 0-9 之间的正整数组成。为了验证此订单号模式,我们将使用minLength
和maxLength
来验证长度,并pattern
使用 来验证模式。
首先,将“minLength”、“maxLength”和“pattern”更改为具有值键的对象,其中您定义的正则表达式模式或数字是值,以及一个message
键,其中值是您的错误消息。
<input
name="order"
ref={register({
required: 'Required',
minLength: {
value: 14,
message: 'Order number too short',
},
maxLength: {
value: 14,
message: 'Order number too long',
},
pattern: {
value: /\d{14}/,
message: "Invalid order number",
},
})}
/>
您的错误看起来如下:
错误就是这样!查看react-hook-form
API文档了解更多信息。
您的 React 表单组件react-hook-form
这是我们最终的 React 表单组件:
想要查看更多涵盖 react-hook-form 丰富功能的代码示例,请访问React Hook Form的网站。想要获取完整版代码以供测试和使用,请查看我们的代码沙盒。
TL;DR:语法总结
我们知道本教程涵盖了表单的大量功能react-hook-form
,因此,为了确保您不会错过任何内容,这里总结了我们所涵盖的功能:
创建一个简单的 React 表单组件
import React from "react";
import { useForm } from "react-hook-form";
import saveData from "./some-other-file";
export default function Form() {
const { register, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(data => saveData(data))}>
<label>Field</label>
<input name="field" ref={register} />
<input type="submit" />
</form>
);
}
为 React 表单组件设置样式
import React from "react";
import { useForm } from "react-hook-form";
import styled from "styled-components";
import saveData from "./some_other_file";
const Styles = styled.div`
background: lavender;
padding: 20px;
h1 {
border-bottom: 1px solid white;
color: #3d3d3d;
font-family: sans-serif;
font-size: 20px;
font-weight: 600;
line-height: 24px;
padding: 10px;
text-align: center;
}
form {
background: white;
border: 1px solid #dedede;
display: flex;
flex-direction: column;
justify-content: space-around;
margin: 0 auto;
max-width: 500px;
padding: 30px 50px;
}
input {
border: 1px solid #d9d9d9;
border-radius: 4px;
box-sizing: border-box;
padding: 10px;
width: 100%;
}
label {
color: #3d3d3d;
display: block;
font-family: sans-serif;
font-size: 14px;
font-weight: 500;
margin-bottom: 5px;
}
.submitButton {
background-color: #6976d9;
color: white;
font-family: sans-serif;
font-size: 14px;
margin: 20px 0px;
}
`;
export function Form() {
const { register, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(data => saveData(data))}>
<label>Field</label>
<input name="field" ref={register} />
<input type="submit" className="submitButton" />
</form>
);
}
export default function App() {
return (
<Styles>
<Form />
</Styles>
);
}
验证你的 React 表单组件
<form onSubmit={handleSubmit(data => saveData(data))}>
<label>Name</label>
<input name="name" ref={register({ required: true })} />
<label>Address</label>
<input
name="address"
ref={register({
required: true,
validate: value => addressValidator(value)
})}
/>
<label>Date</label>
<input
name="date"
type="date"
max={moment().format("YYYY-MM-DD")}
ref={register({ required: true })}
/>
<label>Order Number</label>
<input
name="order"
ref={register({
required: true,
pattern: /\d{14}/,
})}
/>
<input type="submit" />
</form>
将错误添加到您的 React 表单组件
export default function Form() {
const { register, errors, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(data => saveData(data))}>
<label>Field</label>
<input name="field" ref={register({ required: true })} />
{errors.name && "Name is required"}
</form>
);
}
完整形式
import React from "react";
import { useForm } from "react-hook-form";
import styled from "styled-components";
import moment from 'moment';
import saveData from "./some_other_file";
const Styles = styled.div`
background: lavender;
padding: 20px;
h1 {
border-bottom: 1px solid white;
color: #3d3d3d;
font-family: sans-serif;
font-size: 20px;
font-weight: 600;
line-height: 24px;
padding: 10px;
text-align: center;
}
form {
background: white;
border: 1px solid #dedede;
display: flex;
flex-direction: column;
justify-content: space-around;
margin: 0 auto;
max-width: 500px;
padding: 30px 50px;
}
input {
border: 1px solid #d9d9d9;
border-radius: 4px;
box-sizing: border-box;
padding: 10px;
width: 100%;
}
label {
color: #3d3d3d;
display: block;
font-family: sans-serif;
font-size: 14px;
font-weight: 500;
margin-bottom: 5px;
}
.error {
color: red;
font-family: sans-serif;
font-size: 12px;
height: 30px;
}
.submitButton {
background-color: #6976d9;
color: white;
font-family: sans-serif;
font-size: 14px;
margin: 20px 0px;
}
`;
export function addressValidator(address) {
if (address === "123 1st St., New York, NY") {
return true;
}
return false;
}
export function Error({ errors }) {
return <div className={"error"}>{errors ? errors.message : " "}</div>;
}
export function Form() {
const { register, errors, handleSubmit } = useForm();
return (
<form onSubmit={handleSubmit(data => saveData(data))}>
<h1>New Order</h1>
<label>Name</label>
<input name="name" ref={register({ required: 'Required' })} />
<Error errors={errors.name} />
<label>Address</label>
<input
name="address"
ref={register({
required: 'Required',
validate: value => addressValidator(value) || 'Invalid address',
})}
/>
<Error errors={errors.address} />
<label>Date</label>
<input
name="date"
type="date"
max={moment().format("YYYY-MM-DD")}
ref={register({ required: 'Required' })}
/>
<Error errors={errors.date} />
<label>Order Number</label>
<input
name="order"
ref={register({
required: 'Required',
minLength: {
value: 14,
message: 'Order number too short',
},
maxLength: {
value: 14,
message: 'Order number too long',
},
pattern: {
value: /\d{14}/,
message: "Invalid order number",
},
})} />
<Error errors={errors.order} />
<input type="submit" className="submitButton" />
</form>
);
}
export default function App() {
return (
<Styles>
<Form />
</Styles>
);
}
其他 React 表单库
react-hook-form
在GitHub上有近 13K 个 star ,但值得花点时间解释一下为什么我们决定使用它,react-hook-form
而不是其他流行的 React 表单库,比如formik
和react-final-form
。值得注意的是,这些表单库在各自的领域都非常出色:
formik
拥有一流的文档和极其详尽的教程。react-final-form
对于那些习惯使用的人来说非常棒redux-final-form
。
最终,我们选择react-hook-form
它是因为它的包体积小,没有依赖项,而且与其他库相比相对较新(许多来源,例如LogRocket和ITNEXT,都声称它是在 React 中构建表单的最佳库)。如果您有兴趣了解其他构建 React 表单的方法,请查看此处。