作者选择Creative Commons接受捐赠,作为Write for DOnations计划的一部分。
介绍
表单是React Web 应用程序的重要组成部分。它们允许用户在从登录屏幕到结账页面的组件中直接输入和提交数据。由于大多数 React 应用程序是单页应用程序(SPA),或者加载单个页面并通过该页面动态显示新数据的 Web 应用程序,因此您不会直接从表单向服务器提交信息。相反,您将在客户端捕获表单信息并使用其他JavaScript代码发送或显示它。
React 表单提出了一个独特的挑战,因为您可以让浏览器处理大部分表单元素并通过React 更改事件收集数据,或者您可以使用 React 通过直接设置和更新输入值来完全控制元素。第一种方法称为不受控制的组件,因为 React 没有设置值。第二种方法称为受控组件,因为 React 正在主动更新输入。
在本教程中,您将使用 React 构建表单并使用提交购买苹果请求的示例应用程序处理表单提交。您还将了解受控和非受控组件的优缺点。最后,您将根据表单状态动态设置表单属性以启用和禁用字段。在本教程结束时,您将能够使用文本输入、复选框、选择列表等来制作各种表单。
先决条件
-
你需要一个运行Node.js的开发环境;本教程在 Node.js 版本 10.20.1 和 npm 版本 6.14.4 上进行了测试。要在 macOS 或 Ubuntu 18.04 上安装它,请按照如何在 macOS 上安装 Node.js 和创建本地开发环境或如何在 Ubuntu 18.04 上安装 Node.js 的使用 PPA 安装部分中的步骤进行操作。
-
使用Create React App设置的 React 开发环境,删除了非必要的样板。要进行设置,请按照如何管理 React 类组件上的状态教程的步骤 1 — 创建空项目进行操作。本教程将
form-tutorial
用作项目名称。 -
您将使用 React 事件和 Hooks,包括
useState
和useReducer
Hooks。您可以在我们的如何使用 React 处理 DOM 和窗口事件教程以及如何使用 React 组件上的钩子管理状态的钩子中了解事件。 -
您还需要具备 JavaScript 和 HTML 的基本知识,您可以在我们的如何使用 HTML 构建网站系列和如何在 JavaScript 中编码中找到这些知识。CSS 的基本知识也很有用,您可以在Mozilla 开发人员网络找到这些知识。
第 1 步 – 使用 JSX 创建一个基本表单
在这一步中,您将使用JSX创建一个包含单个元素和提交按钮的空表单。您将处理表单提交事件并将数据传递给另一个服务。在此步骤结束时,您将拥有一个基本表单,该表单将向异步函数提交数据。
首先,打开App.js
:
- nano src/components/App/App.js
您将构建一个用于购买苹果的表单。创建<div>
具有className
的<wrapper>
。然后通过添加以下突出显示的代码添加一个<h1>
带有文本“How About Them Apples”的标签和一个空form
元素:
import React from 'react';
import './App.css';
function App() {
return (
<div className="wrapper">
<h1>How About Them Apples</h1>
<form>
</form>
</div>
)
}
export default App;
接下来,在<form>
标签内,添加一个<fieldset>
元素<input>
被<label>
标签包围的元素。通过<input>
用<label>
标签包装元素,您可以通过将标签与输入相关联来帮助屏幕阅读器。这将增加您的应用程序的可访问性。
最后,<button>
在表单底部添加一个提交:
import React from 'react';
import './App.css';
function App() {
return(
<div className="wrapper">
<h1>How About Them Apples</h1>
<form>
<fieldset>
<label>
<p>Name</p>
<input name="name" />
</label>
</fieldset>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default App;
保存并关闭文件。然后打开App.css
设置样式:
- nano src/components/App/App.css
添加padding
到.wrapper
和margin
以fieldset
在元素之间留出一些空间:
.wrapper {
padding: 5px 20px;
}
.wrapper fieldset {
margin: 20px 0;
}
保存并关闭文件。当您这样做时,浏览器将重新加载,您将看到一个基本表单。
如果您单击提交按钮,页面将重新加载。由于您正在构建单页应用程序,因此您将阻止带有type="submit"
. 相反,您将submit
在组件内部处理事件。
打开App.js
:
- nano src/components/App/App.js
要处理事件,您需要向<form>
元素添加事件处理程序,而不是<button>
. 创建一个调用的函数handleSubmit
,该函数将SyntheticEvent
作为参数。该SyntheticEvent
是围绕标准的包装Event
物,并包含相同的接口。调用.preventDefault
以停止页面提交表单然后触发alert
以显示表单已提交:
import React from 'react';
import './App.css';
function App() {
const handleSubmit = event => {
event.preventDefault();
alert('You have submitted the form.')
}
return(
<div className="wrapper">
<h1>How About Them Apples</h1>
<form onSubmit={handleSubmit}>
<fieldset>
<label>
<p>Name</p>
<input name="name" />
</label>
</fieldset>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default App;
保存文件。当您这样做时,浏览器将重新加载。如果单击提交按钮,则会弹出警报,但不会重新加载窗口。
在许多 React 应用程序中,您会将数据发送到外部服务,例如 Web API。当服务解决时,您通常会显示成功消息、重定向用户或同时执行这两种操作。
要模拟 API,请在setTimeout
函数中添加一个handleSubmit
函数。这将创建一个异步操作,在完成之前等待一段时间,其行为类似于对外部数据的请求。然后使用useState
Hook 创建一个submitting
变量和一个setSubmitting
函数。呼叫setSubmitting(true)
当数据被提交和呼叫setSubmitting(false)
时,超时被解决:
import React, { useState } from 'react';
import './App.css';
function App() {
const [submitting, setSubmitting] = useState(false);
const handleSubmit = event => {
event.preventDefault();
setSubmitting(true);
setTimeout(() => {
setSubmitting(false);
}, 3000)
}
return(
<div className="wrapper">
<h1>How About Them Apples</h1>
{submitting &&
<div>Submtting Form...</div>
}
<form onSubmit={handleSubmit}>
<fieldset>
<label>
<p>Name</p>
<input name="name" />
</label>
</fieldset>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default App;
此外,你会提醒他们的形式通过显示时会显示HTML短消息提交用户submitting
的true
。
保存文件。当您这样做时,浏览器将重新加载,您将在提交时收到消息:
现在你有一个基本的表单来处理 React 组件中的提交事件。您已使用onSubmit
事件处理程序将其连接到 JSX,并且您正在使用 Hooks在事件运行时有条件地显示警报handleSubmit
。
在下一步中,您将添加更多用户输入并将数据保存到用户填写表单时的状态。
步骤 2 — 使用不受控制的组件收集表单数据
在此步骤中,您将使用不受控制的组件收集表单数据。不受控制的组件是没有value
React 设置的组件。您将连接到onChange
事件以收集用户输入,而不是在组件上设置数据。在构建组件时,您将了解 React 如何处理不同的输入类型,以及如何创建可重用的函数来将表单数据收集到单个对象中。
在此步骤结束时,您将能够使用不同的表单元素(包括下拉列表和复选框)构建表单。您还可以收集、提交和显示表单数据。
注意:在大多数情况下,您将为 React 应用程序使用受控组件。但是从不受控制的组件开始是一个好主意,这样您就可以避免在错误设置值时可能引入的细微错误或意外循环。
目前,您有一个可以提交信息的表单,但没有什么可提交的。表单只有一个<input>
元素,但您并未在组件的任何位置收集或存储数据。为了能够在用户提交表单时存储和处理数据,您需要创建一种管理 state 的方法。然后,您需要使用事件处理程序连接到每个输入。
在 内部App.js
,使用useReducer
Hook 创建一个formData
对象和一个setFormData
函数。对于reducer 函数,从对象中拉出name
and并通过在末尾添加and 的同时传播当前状态来更新the 。这将创建一个状态对象,该对象保留当前状态,同时在更改时覆盖特定值:value
event.target
state
name
value
import React, { useReducer, useState } from 'react';
import './App.css';
const formReducer = (state, event) => {
return {
...state,
[event.target.name]: event.target.value
}
}
function App() {
const [formData, setFormData] = useReducer(formReducer, {});
const [submitting, setSubmitting] = useState(false);
const handleSubmit = event => {
event.preventDefault();
setSubmitting(true);
setTimeout(() => {
setSubmitting(false);
}, 3000)
}
return(
<div className="wrapper">
<h1>How About Them Apples</h1>
{submitting &&
<div>Submtting Form...</div>
}
<form onSubmit={handleSubmit}>
<fieldset>
<label>
<p>Name</p>
<input name="name" onChange={setFormData}/>
</label>
</fieldset>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default App;
制作减速器后,添加setFormData
到onChange
输入的事件处理程序中。保存文件。当您这样做时,浏览器将重新加载。但是,如果您尝试输入输入,则会出现错误:
问题是SyntheticEvent
被重用并且不能传递给异步函数。换句话说,你不能直接传递事件。要解决此问题,您需要在调用 reducer 函数之前提取所需的数据。
更新 reducer 函数以获取具有name
and属性的对象value
。然后创建一个调用的函数handleChange
,该函数从 中提取数据event.target
并将对象传递给setFormData
。最后,更新onChange
事件处理程序以使用新函数:
import React, { useReducer, useState } from 'react';
import './App.css';
const formReducer = (state, event) => {<^>
return {
...state,
[event.name]: event.value
}
}
function App() {
const [formData, setFormData] = useReducer(formReducer, {});
const [submitting, setSubmitting] = useState(false);
const handleSubmit = event => {
event.preventDefault();
setSubmitting(true);
setTimeout(() => {
setSubmitting(false);
}, 3000);
}
const handleChange = event => {
setFormData({
name: event.target.name,
value: event.target.value,
});
}
return(
<div className="wrapper">
<h1>How About Them Apples</h1>
{submitting &&
<div>Submtting Form...</div>
}
<form onSubmit={handleSubmit}>
<fieldset>
<label>
<p>Name</p>
<input name="name" onChange={handleChange}/>
</label>
</fieldset>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default App;
保存文件。当您这样做时,页面将刷新,您将能够输入数据。
现在您正在收集表单状态,更新用户显示消息以在无序列表 ( <ul>
) 元素中显示数据。
将数据转换成一个阵列使用Object.entries
,则映射数据的阵列的每个成员转换为在<li>
元件的名称和值。一定要使用name
作为key
元素的道具:
...
return(
<div className="wrapper">
<h1>How About Them Apples</h1>
{submitting &&
<div>
You are submitting the following:
<ul>
{Object.entries(formData).map(([name, value]) => (
<li key={name}><strong>{name}</strong>:{value.toString()}</li>
))}
</ul>
</div>
}
<form onSubmit={handleSubmit}>
<fieldset>
<label>
<p>Name</p>
<input name="name" onChange={handleChange}/>
</label>
</fieldset>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default App;
保存文件。当您这样做时,页面将重新加载,您将能够输入和提交数据:
现在您有了一个基本表单,您可以添加更多元素。创建另一个<fieldset>
元素并<select>
为每个元素添加一个具有不同苹果品种的元素<option>
,一个<input>
带有 atype="number"
和 astep="1"
以获得递增 1 的计数,以及一个<input>
带有type="checkbox"
礼物包装选项的元素。
对于每个元素,将handleChange
函数添加到onChange
事件处理程序:
...
return(
<div className="wrapper">
<h1>How About Them Apples</h1>
{submitting &&
<div>
You are submitting the following:
<ul>
{Object.entries(formData).map(([name, value]) => (
<li key={name}><strong>{name}</strong>: {value.toString()}</li>
))}
</ul>
</div>
}
<form onSubmit={handleSubmit}>
<fieldset>
<label>
<p>Name</p>
<input name="name" onChange={handleChange}/>
</label>
</fieldset>
<fieldset>
<label>
<p>Apples</p>
<select name="apple" onChange={handleChange}>
<option value="">--Please choose an option--</option>
<option value="fuji">Fuji</option>
<option value="jonathan">Jonathan</option>
<option value="honey-crisp">Honey Crisp</option>
</select>
</label>
<label>
<p>Count</p>
<input type="number" name="count" onChange={handleChange} step="1"/>
</label>
<label>
<p>Gift Wrap</p>
<input type="checkbox" name="gift-wrap" onChange={handleChange} />
</label>
</fieldset>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default App;
保存文件。当您这样做时,页面将重新加载,您的表单将拥有多种输入类型:
这里有一种特殊情况需要考虑。该value
用于礼品包装的复选框将永远是"on"
,不管项目是否被选中与否。value
您需要使用checked
属性,而不是使用事件的。
更新handleChange
函数以查看是否event.target.type
为checkbox
。如果是,则将event.target.checked
属性作为value
而不是event.target.value
:
import React, { useReducer, useState } from 'react';
import './App.css';
...
function App() {
const [formData, setFormData] = useReducer(formReducer, {});
const [submitting, setSubmitting] = useState(false);
const handleSubmit = event => {
event.preventDefault();
setSubmitting(true);
setTimeout(() => {
setSubmitting(false);
}, 3000);
}
const handleChange = event => {
const isCheckbox = event.target.type === 'checkbox';
setFormData({
name: event.target.name,
value: isCheckbox ? event.target.checked : event.target.value,
})
}
...
在此代码中,您使用?
三元运算符来创建条件语句。
保存文件。浏览器刷新后,填写表单并点击提交。您会发现警报与表单中的数据相匹配:
在这一步中,您学习了如何创建不受控制的表单组件。您使用useReducer
Hook将表单数据保存到一个状态,并在不同的组件中重用该数据。您还添加了不同类型的表单组件并调整了您的功能以根据元素类型保存正确的数据。
在下一步中,您将通过动态设置组件值将组件转换为受控组件。
第 3 步 – 使用受控组件更新表单数据
在此步骤中,您将使用受控组件动态设置和更新数据。您将为每个组件添加一个value
道具以设置或更新表单数据。您还将在提交时重置表单数据。
在这一步结束时,您将能够使用 React 状态和道具动态控制表单数据。
使用不受控制的组件,您不必担心同步数据。您的应用程序将始终保留最新的更改。但是在很多情况下,您需要同时读取和写入输入组件。为此,您需要组件的值是动态的。
在上一步中,您提交了一个表单。但是表单提交成功后,表单仍然包含旧的陈旧数据。要从每个输入中擦除数据,您需要将组件从不受控制的组件更改为受控组件。
受控组件类似于非受控组件,但 React 更新了value
prop。不利的一面是,如果您不小心并且没有正确更新value
prop,该组件将出现损坏并且似乎不会更新。
在这种形式中,您已经在存储数据,因此要转换组件,您将value
使用formData
状态中的数据更新prop 。但是,有一个问题:value
不能是undefined
. 如果您的值为undefined
,您将在控制台中收到错误消息。
由于您的初始状态是一个空对象,您需要将值设置为来自的值formData
或默认值,例如空字符串。例如,名称的值为formData.name || ''
:
...
return(
<div className="wrapper">
<h1>How About Them Apples</h1>
{submitting &&
<div>
You are submitting the following:
<ul>
{Object.entries(formData).map(([name, value]) => (
<li key={name}><strong>{name}</strong>: {value.toString()}</li>
))}
</ul>
</div>
}
<form onSubmit={handleSubmit}>
<fieldset>
<label>
<p>Name</p>
<input name="name" onChange={handleChange} value={formData.name || ''}/>
</label>
</fieldset>
<fieldset>
<label>
<p>Apples</p>
<select name="apple" onChange={handleChange} value={formData.apple || ''}>
<option value="">--Please choose an option--</option>
<option value="fuji">Fuji</option>
<option value="jonathan">Jonathan</option>
<option value="honey-crisp">Honey Crisp</option>
</select>
</label>
<label>
<p>Count</p>
<input type="number" name="count" onChange={handleChange} step="1" value={formData.count || ''}/>
</label>
<label>
<p>Gift Wrap</p>
<input type="checkbox" name="gift-wrap" onChange={handleChange} checked={formData['gift-wrap'] || false}/>
</label>
</fieldset>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default App;
和以前一样,复选框有点不同。您需要设置checked
属性而不是设置值。如果该属性为真,浏览器将显示该框为选中状态。使用 将初始checked
属性设置为 false formData['gift-wrap'] || false
。
如果要预先填写表单,请向formData
状态添加一些默认数据。设置为默认值count
给予formState
的默认值{ count: 100 }
。您还可以在初始对象中设置默认值,但您需要在显示表单信息之前过滤掉虚假值:
...
function App() {
const [formData, setFormData] = useReducer(formReducer, {
count: 100,
});
const [submitting, setSubmitting] = useState(false);
...
保存文件。当您这样做时,浏览器将重新加载,您将看到带有默认数据的输入:
注意:该value
属性placeholder
与浏览器原生的属性不同。该placeholder
属性显示信息,但一旦用户进行更改就会消失;它不存储在组件上。您可以主动编辑value
,但 aplaceholder
只是用户的指南。
现在您拥有活动组件,您可以在提交时清除数据。为此,请在您的formReducer
. 如果event.reset
为真,则为每个表单元素返回一个具有空值的对象。确保为每个输入添加一个值。如果返回空对象或不完整的对象,则组件不会更新,因为值为undefined
。
在 中添加新事件条件后formReducer
,更新提交函数以在函数解析时重置状态:
import React, { useReducer, useState } from 'react';
import './App.css';
const formReducer = (state, event) => {
if(event.reset) {
return {
apple: '',
count: 0,
name: '',
'gift-wrap': false,
}
}
return {
...state,
[event.name]: event.value
}
}
function App() {
const [formData, setFormData] = useReducer(formReducer, {
count: 100
});
const [submitting, setSubmitting] = useState(false);
const handleSubmit = event => {
event.preventDefault();
setSubmitting(true);
setTimeout(() => {
setSubmitting(false);
setFormData({
reset: true
})
}, 3000);
}
...
保存文件。当您这样做时,浏览器将重新加载并且表单将在提交时清除。
在此步骤中,您通过动态设置value
或checked
属性将不受控制的组件转换为受控组件。您还学习了如何通过设置默认状态来重新填充数据,以及如何通过更新表单缩减器以返回默认值来清除数据。
在下一步中,您将动态设置表单组件属性并在提交时禁用表单。
第 4 步 – 动态更新表单属性
在此步骤中,您将动态更新表单元素属性。您将根据之前的选择设置属性,并在提交期间禁用表单以防止意外多次提交。
目前,每个组件都是静态的。它们不会随着形式的变化而变化。在大多数应用程序中,表单是动态的。字段将根据以前的数据发生变化。他们将验证并显示错误。当您填充其他组件时,它们可能会消失或扩大。
像大多数 React 组件一样,您可以动态设置组件的属性和属性,它们会随着数据的变化而重新渲染。
尝试将输入设置为 ,disabled
直到另一个输入满足条件。更新礼品包装复选框以禁用,除非用户选择该fuji
选项。
在里面App.js
,将disabled
属性添加到复选框。如果是,formData.apple
则使属性为真fuji
:
...
<fieldset>
<label>
<p>Apples</p>
<select name="apple" onChange={handleChange} value={formData.apple || ''}>
<option value="">--Please choose an option--</option>
<option value="fuji">Fuji</option>
<option value="jonathan">Jonathan</option>
<option value="honey-crisp">Honey Crisp</option>
</select>
</label>
<label>
<p>Count</p>
<input type="number" name="count" onChange={handleChange} step="1" value={formData.count || ''}/>
</label>
<label>
<p>Gift Wrap</p>
<input
checked={formData['gift-wrap'] || false}
disabled={formData.apple !== 'fuji'}
name="gift-wrap"
onChange={handleChange}
type="checkbox"
/>
</label>
</fieldset>
<button type="submit">Submit</button>
</form>
</div>
)
}
export default App;
保存文件。当您这样做时,浏览器将重新加载,并且默认情况下将禁用复选框:
如果选择苹果类型的Fuji,该元素将被启用:
除了更改单个组件的属性外,您还可以通过更新fieldset
组件来修改整个组件组。
例如,您可以在表单主动提交时禁用该表单。这将防止重复提交并防止用户在handleSubmit
函数完全解析之前更改字段。
添加disabled={submitting}
到每个<fieldset>
元素和<button>
元素:
...
<form onSubmit={handleSubmit}>
<fieldset disabled={submitting}>
<label>
<p>Name</p>
<input name="name" onChange={handleChange} value={formData.name || ''}/>
</label>
</fieldset>
<fieldset disabled={submitting}>
<label>
<p>Apples</p>
<select name="apple" onChange={handleChange} value={formData.apple || ''}>
<option value="">--Please choose an option--</option>
<option value="fuji">Fuji</option>
<option value="jonathan">Jonathan</option>
<option value="honey-crisp">Honey Crisp</option>
</select>
</label>
<label>
<p>Count</p>
<input type="number" name="count" onChange={handleChange} step="1" value={formData.count || ''}/>
</label>
<label>
<p>Gift Wrap</p>
<input
checked={formData['gift-wrap'] || false}
disabled={formData.apple !== 'fuji'}
name="gift-wrap"
onChange={handleChange}
type="checkbox"
/>
</label>
</fieldset>
<button type="submit" disabled={submitting}>Submit</button>
</form>
</div>
)
}
export default App;
保存文件,浏览器将刷新。当您提交表单时,这些字段将被禁用,直到提交功能解决:
您可以更新输入组件上的任何属性。如果您需要更改maxvalue
数字输入或需要添加动态pattern
属性以进行验证,这将很有帮助。
在此步骤中,您将在表单组件上动态设置属性。您添加了一个属性以根据来自另一个组件的输入动态启用或禁用一个组件,并使用该<fieldset>
组件禁用了整个部分。
结论
Forms are key to rich web applications. In React, you have different options for connecting and controlling forms and elements. Like other components, you can dynamically update properties including the value
input elements. Uncontrolled components are best for simplicity, but might not fit situations when a component needs to be cleared or pre-populated with data. Controlled components give you more opportunities to update the data, but can add another level of abstraction that may cause unintentional bugs or re-renders.
Regardless of your approach, React gives you the ability to dynamically update and adapt your forms to the needs of your application and your users.
如果您想阅读更多 React 教程,请查看我们的React 主题页面,或返回“如何在 React.js 中编码”系列页面。