如何在 React 中构建表单

_ 作者选择了 Creative Commons以作为 Write for Donations计划的一部分接收捐款。

介绍

形式是反应网络应用程序的关键组成部分. 它们允许用户直接输入和提交从登录屏幕到取出页面等组件的数据. 由于大多数回放应用程序是_single page applications_(SPA),或装入单页并动态显示新数据的网络应用程序,所以不会将信息直接从表单提交到服务器. 相反,您会在客户端获取表单信息,并使用额外的JavaScript代码发送或显示.

React格式提出了独特的挑战,因为您可以允许浏览器处理大部分的形式元素,并通过[React change 事件 (https://andsky.com/tech/tutorials/how-to-handle-dom-and-window-events-with-react)收集数据,也可以使用React来通过直接设置和更新输入值来完全控制元素. 第一个方法叫做_不受控制的组件_因为React没有设置值. 第二种方法称为_可控组件_因为React正在积极更新输入.

在本教程中,您将使用 React 构建表单,并使用示例应用程序处理表单提交,该应用程序提交购买苹果的请求。您还将了解受控和非受控组件的优点和缺点。 最后,您将动态设置表单属性以启用和禁用取决于表单状态的字段。 在本教程结束时,您将能够使用文本输入,检查框,选择列表等创建各种形式。

前提条件

步骤 1 — 使用 JSX 创建基本表单

在此步骤中,您将使用 [JSX] 创建一个单元格和提交按钮的空格表单(https://andsky.com/tech/tutorials/how-to-create-react-elements-with-jsx)。 您将处理提交表单事件并将数据传输到另一个服务。 在此步骤的结束时,您将有一个基本表单,将提交数据到 asynchronous function

首先,打开App.js:

1nano src/components/App/App.js

您将建立一个购买苹果的表格。 创建一个 <div> 具有 className<wrapper>. 然后添加一个 <h1> 标签与文本 "How About Them Apples" 和一个空的 form 元素,通过添加以下突出代码:

 1[label form-tutorial/src/components/App/App.js]
 2
 3import React from 'react';
 4import './App.css';
 5
 6function App() {
 7  return (
 8    <div className="wrapper">
 9      <h1>How About Them Apples</h1>
10      <form>
11      </form>
12    </div>
13  )
14}
15
16export default App;

接下来,在<form>标签中,添加一个<fieldset>元素,其中包含一个<input>元素,周围有一个<label>标签。

最后,在表单底部添加一个提交<按钮>:

 1[label form-tutorial/src/components/App/App.js]
 2
 3import React from 'react';
 4import './App.css';
 5
 6function App() {
 7  return(
 8    <div className="wrapper">
 9      <h1>How About Them Apples</h1>
10      <form>
11      <fieldset>
12         <label>
13           <p>Name</p>
14           <input name="name" />
15         </label>
16       </fieldset>
17       <button type="submit">Submit</button>
18      </form>
19    </div>
20  )
21}
22
23export default App;

保存并关闭文件. 然后打开App.css来设置样式:

1nano src/components/App/App.css

.wrapper添加paddingmarginfieldset,在元素之间提供一些空间:

1[label form-tutorial/src/components/App/App.css]
2.wrapper {
3    padding: 5px 20px;
4}
5
6.wrapper fieldset {
7    margin: 20px 0;
8}

保存和关闭文件. 当你这样做时,浏览器将重新加载,你会看到一个基本的表格。

Basic form with a field for "name" and a submit button

如果你点击提交按钮,页面将重新加载,因为你正在构建一个单页应用程序,你将阻止一个按钮的这个标准行为,用一个提交按钮。

打开App.js:

1nano src/components/App/App.js

为处理事件,您会在QQformQQ元素中添加一个事件处理器,而不是QQ按钮. 创建一个名为andleSubmit'的函数,该函数将以[Synthetic Event' (https://andsky.com/tech/tutorials/how-to-handle-dom-and-window-events-with-react# step-1-%E2%80%94-extracting-event-data-with-syntheticevent)作为论据。 " 合成活动 " 是一个围绕标准 " Event " 对象并包含同一接口的包装器。 调用. prevent Default' 阻止页面提交表格,然后触发提醒 ' ,以显示提交表格:

 1[label form-tutorial/src/components/App/App.js]
 2
 3import React from 'react';
 4import './App.css';
 5
 6function App() {
 7  const handleSubmit = event => {
 8   event.preventDefault();
 9   alert('You have submitted the form.')
10 }
11
12  return(
13    <div className="wrapper">
14      <h1>How About Them Apples</h1>
15      <form onSubmit={handleSubmit}>
16        <fieldset>
17          <label>
18            <p>Name</p>
19            <input name="name" />
20          </label>
21        </fieldset>
22        <button type="submit">Submit</button>
23      </form>
24    </div>
25  )
26}
27
28export default App;

保存文件. 当你这样做时,浏览器将重新加载. 如果你点击提交按钮,警告将出现,但窗口不会重新加载。

Form submit alert

在许多 React 应用程序中,您会将数据发送到外部服务中,例如 Web API

要模拟一个API,请在)函数。 这将创建一个同步操作,在完成前等待一定时间,其行为类似于对外部数据的请求. 然后使用),并拨打setSubmitit(假)` 当超时时:

 1[label form-tutorial/src/components/App/App.js]
 2
 3import React, { useState } from 'react';
 4import './App.css';
 5
 6function App() {
 7  const [submitting, setSubmitting] = useState(false);
 8  const handleSubmit = event => {
 9    event.preventDefault();
10   setSubmitting(true);
11
12   setTimeout(() => {
13     setSubmitting(false);
14   }, 3000)
15 }
16
17  return(
18    <div className="wrapper">
19      <h1>How About Them Apples</h1>
20      {submitting &&
21       <div>Submtting Form...</div>
22     }
23      <form onSubmit={handleSubmit}>
24        <fieldset>
25          <label>
26            <p>Name</p>
27            <input name="name" />
28          </label>
29        </fieldset>
30        <button type="submit">Submit</button>
31      </form>
32    </div>
33  )
34}
35
36export default App;

此外,您还会通过在HTML中显示一个短消息来提醒用户他们的表单正在提交,该消息会在提交真实时显示。

保存文件. 当您这样做时,浏览器将重新加载,您将收到提交的消息:

Form submitting shows message for 3 seconds

你已经连接到你的 JSX 使用 onSubmit 事件处理器,你正在使用 Hooks 来(https://andsky.com/tech/tutorials/how-to-write-conditional-statements-in-javascript)显示一个警告,而 handleSubmit 事件正在运行。

在下一步中,您将添加更多用户输入,并将数据保存到用户填写表单时。

步骤 2 — 使用未经控制的组件收集表单数据

在此步骤中,您将使用 _uncontrol 组件_来收集窗体数据. 一个不受控制的组成部分是一个没有反应设定的 " 价值 " 的组成部分。 与其设定组件数据,不如连接到"onChange"事件来收集用户输入. 在构建组件时,会学习React如何处理不同的输入类型,以及如何创建一个可重复使用的功能来将数据收集成单一的对象.

到此步骤结束时,您将能够使用不同的表单元素构建表单,包括下载和检查框,您还可以收集、提交和显示表单数据。

注意:在大多数情况下,您将为您的 React 应用程序使用受控组件,但最好从未受控制的组件开始,这样您就可以避免错误设置值时可能引入的微妙错误或随机循环。

目前,您有一个可以提交信息的表单,但没有任何可提交的信息。该表单有一个单一的)。

App.js'内,使用减少使用量 ' 钩来创建)更新状态',同时在结尾处添加名称'和`价值'。 这将创建一个保存当前状态的状态对象, 同时在修改时覆盖特定值 :

 1[label form-tutorial/src/components/App/App.js]
 2import React, { useReducer, useState } from 'react';
 3import './App.css';
 4
 5const formReducer = (state, event) => {
 6 return {
 7   ...state,
 8   [event.target.name]: event.target.value
 9 }
10}
11
12function App() {
13  const [formData, setFormData] = useReducer(formReducer, {});
14  const [submitting, setSubmitting] = useState(false);
15
16  const handleSubmit = event => {
17    event.preventDefault();
18    setSubmitting(true);
19
20    setTimeout(() => {
21      setSubmitting(false);
22    }, 3000)
23  }
24
25  return(
26    <div className="wrapper">
27      <h1>How About Them Apples</h1>
28      {submitting &&
29        <div>Submtting Form...</div>
30      }
31      <form onSubmit={handleSubmit}>
32        <fieldset>
33          <label>
34            <p>Name</p>
35            <input name="name" onChange={setFormData}/>
36          </label>
37        </fieldset>
38        <button type="submit">Submit</button>
39      </form>
40    </div>
41  )
42}
43
44export default App;

在创建减速器后,在输入中添加setFormDataonChange事件处理器. 保存文件. 当你这样做时,浏览器将重新加载. 但是,如果你尝试输入,你会收到一个错误:

Error with SyntheticEvent

問題在於「SyntheticEvent」是 重用並不能傳送到非同步函數。 換句話說,你不能直接傳送該事件。 要解決這個問題,你需要提取你需要的數據,在呼叫減少函數之前。

更新减量函数以选择一个具有名称属性的对象,然后创建一个名为handleChange的函数,该函数将数据从event.target拉出并将对象传输到setFormData

 1[label form-tutorial/src/components/App/App.js]
 2import React, { useReducer, useState } from 'react';
 3import './App.css';
 4
 5const formReducer = (state, event) => {
 6 return {
 7   ...state,
 8   [event.name]: event.value
 9 }
10}
11
12function App() {
13  const [formData, setFormData] = useReducer(formReducer, {});
14  const [submitting, setSubmitting] = useState(false);
15
16  const handleSubmit = event => {
17    event.preventDefault();
18    setSubmitting(true);
19
20    setTimeout(() => {
21      setSubmitting(false);
22    }, 3000);
23  }
24
25  const handleChange = event => {
26    setFormData({
27      name: event.target.name,
28      value: event.target.value,
29    });
30  }
31
32  return(
33    <div className="wrapper">
34      <h1>How About Them Apples</h1>
35      {submitting &&
36        <div>Submtting Form...</div>
37      }
38      <form onSubmit={handleSubmit}>
39        <fieldset>
40          <label>
41            <p>Name</p>
42            <input name="name" onChange={handleChange}/>
43          </label>
44        </fieldset>
45        <button type="submit">Submit</button>
46      </form>
47    </div>
48  )
49}
50
51export default App;

保存文件. 当你这样做时,页面将更新,你将能够输入数据。

现在,当您正在收集表单状态时,请更新用户显示消息以显示未分类列表(<ul>)元素中的数据。

将数据转换为 array 使用 Object.entries,然后将数据转换为 map 将数据转换为每个数组成员的 <li> 元素与名称和值。 请确保使用 name 作为元素的 key 支持:

 1[label form-tutorial/src/components/App/App.js]
 2...
 3  return(
 4    <div className="wrapper">
 5      <h1>How About Them Apples</h1>
 6      {submitting &&
 7       <div>
 8         You are submitting the following:
 9         <ul>
10           {Object.entries(formData).map(([name, value]) => (
11             <li key={name}><strong>{name}</strong>:{value.toString()}</li>
12           ))}
13         </ul>
14       </div>
15      }
16      <form onSubmit={handleSubmit}>
17        <fieldset>
18          <label>
19            <p>Name</p>
20            <input name="name" onChange={handleChange}/>
21          </label>
22        </fieldset>
23        <button type="submit">Submit</button>
24      </form>
25    </div>
26  )
27}
28
29export default App;

当你这样做时,页面将重新加载,你将能够输入和提交数据:

Fill out the form and submit

现在你有一个基本形式,你可以添加更多的元素. 创建另一个 <fieldset> 元素,并为每个 <option> 添加一个 <select> 元素与不同的苹果品种,一个 <input> 具有 type="number' 和一个 step="1"以获得增加 1 的计数,一个具有type="checkbox" 以获得礼品包装选项。

对于每个元素,向onChange事件处理器添加handleChange函数:

 1[label form-tutorial/src/components/App/App.js]
 2...
 3  return(
 4    <div className="wrapper">
 5      <h1>How About Them Apples</h1>
 6      {submitting &&
 7        <div>
 8          You are submitting the following:
 9          <ul>
10            {Object.entries(formData).map(([name, value]) => (
11              <li key={name}><strong>{name}</strong>: {value.toString()}</li>
12            ))}
13          </ul>
14        </div>
15      }
16      <form onSubmit={handleSubmit}>
17        <fieldset>
18          <label>
19            <p>Name</p>
20            <input name="name" onChange={handleChange}/>
21          </label>
22        </fieldset>
23        <fieldset>
24         <label>
25           <p>Apples</p>
26           <select name="apple" onChange={handleChange}>
27               <option value="">--Please choose an option--</option>
28               <option value="fuji">Fuji</option>
29               <option value="jonathan">Jonathan</option>
30               <option value="honey-crisp">Honey Crisp</option>
31           </select>
32         </label>
33         <label>
34           <p>Count</p>
35           <input type="number" name="count" onChange={handleChange} step="1"/>
36         </label>
37         <label>
38           <p>Gift Wrap</p>
39           <input type="checkbox" name="gift-wrap" onChange={handleChange} />
40         </label>
41       </fieldset>
42        <button type="submit">Submit</button>
43      </form>
44    </div>
45  )
46}
47
48export default App;

当您这样做时,页面将重新加载,您将有各种输入类型为您的表单:

Form with all input types

这里有一个特殊情况要考虑。礼品包装检查框的将始终是启用,无论该项目是否已被检查。

更新handleChange函数以查看event.target.type是否为checkbox。如果是,请将event.target.checked属性作为,而不是event.target.value:

 1[label form-tutorial/src/components/App/App.js]
 2import React, { useReducer, useState } from 'react';
 3import './App.css';
 4
 5...
 6
 7function App() {
 8  const [formData, setFormData] = useReducer(formReducer, {});
 9  const [submitting, setSubmitting] = useState(false);
10
11  const handleSubmit = event => {
12    event.preventDefault();
13    setSubmitting(true);
14
15    setTimeout(() => {
16      setSubmitting(false);
17    }, 3000);
18  }
19
20  const handleChange = event => {
21   const isCheckbox = event.target.type === 'checkbox';
22   setFormData({
23     name: event.target.name,
24     value: isCheckbox ? event.target.checked : event.target.value,
25   })
26 }
27...

在此代码中,您使用 ? ternary 操作员来执行条件声明。

保存文件. 在浏览器更新后,填写表格并点击提交. 你会发现警告匹配表格中的数据:

Form elements submitting correct data

在此步骤中,您了解了如何创建不受控制的表单组件. 您将表单数据保存为一个状态,使用使用减少器链接,并将这些数据重复使用到不同的组件中。

在下一步中,您将通过动态设置组件值来将组件转换为受控组件。

步骤 3 — 使用受控组件更新表单数据

在此步骤中,您将使用受控制的组件动态设置和更新数据. 您将向每个组件添加一个 value prop 以设置或更新表单数据. 您还将重新设置表单数据。

到此步骤结束时,您将能够使用 React 状态和特许权动态控制表单数据。

对于未经控制的组件,您不必担心同步数据,您的应用程序将始终保持最新的更改,但有许多情况下,您需要读取和写入输入组件。

在之前的步骤中,您提交了表单,但在表单提交成功后,表单仍然包含旧的数据,要从每个输入中删除数据,您需要将组件从不受控制的组件更改为受控制的组件。

一个受控制的组件类似于一个不受控制的组件,但React更新了支持,而缺点是,如果您不小心并不正确地更新支持,则该组件将出现破坏并似乎不会更新。

在此表单中,您已经存储了数据,因此要将组件转换,您将更新插件以从formData状态的数据。

由于您的初始状态是空对象,您需要将该值设置为formData的值或默认值,例如空串。

 1[label form-tutorial/src/components/App/App.js]
 2...
 3  return(
 4    <div className="wrapper">
 5      <h1>How About Them Apples</h1>
 6      {submitting &&
 7        <div>
 8          You are submitting the following:
 9          <ul>
10            {Object.entries(formData).map(([name, value]) => (
11              <li key={name}><strong>{name}</strong>: {value.toString()}</li>
12            ))}
13          </ul>
14        </div>
15      }
16      <form onSubmit={handleSubmit}>
17        <fieldset>
18          <label>
19            <p>Name</p>
20            <input name="name" onChange={handleChange} value={formData.name || ''}/>
21          </label>
22        </fieldset>
23        <fieldset>
24          <label>
25            <p>Apples</p>
26            <select name="apple" onChange={handleChange} value={formData.apple || ''}>
27                <option value="">--Please choose an option--</option>
28                <option value="fuji">Fuji</option>
29                <option value="jonathan">Jonathan</option>
30                <option value="honey-crisp">Honey Crisp</option>
31            </select>
32          </label>
33          <label>
34            <p>Count</p>
35            <input type="number" name="count" onChange={handleChange} step="1" value={formData.count || ''}/>
36          </label>
37          <label>
38            <p>Gift Wrap</p>
39            <input type="checkbox" name="gift-wrap" onChange={handleChange} checked={formData['gift-wrap'] || false}/>
40          </label>
41        </fieldset>
42        <button type="submit">Submit</button>
43      </form>
44    </div>
45  )
46}
47
48export default App;

和以前一样,检查框略有不同,而不是设置一个值,您需要设置检查属性。如果属性是真实的,浏览器会将该框显示为已检查的。 将最初的检查属性设置为错误,使用`formData['gift-wrap']

如果要预先填写表单,请将一些默认数据添加到formData状态。 通过给formState一个默认值为{ count: 100}来为设置默认值。

1[label form-tutorial/src/components/App/App.js]
2...
3
4function App() {
5  const [formData, setFormData] = useReducer(formReducer, {
6   count: 100,
7 });
8  const [submitting, setSubmitting] = useState(false);
9...

当你这样做时,浏览器将重新加载,你会看到输入与默认数据:

Form with default count

<$>[注] 注: 属性与位置保持者属性不同,该属性是浏览器的原始属性。位置保持者属性显示信息,但在用户进行更改后就会消失;它不会存储在组件上。

现在你已经有活跃的组件,你可以清除在提交时的数据。 要做到这一点,请在formReducer中添加一个新的条件。 如果event.reset是真实的,请为每个表单元素返回一个包含空值的对象。

formReducer中添加新的事件条件后,更新提交函数以在函数解决时重置状态:

 1[label form-tutorial/src/components/App/App.js]
 2import React, { useReducer, useState } from 'react';
 3import './App.css';
 4
 5const formReducer = (state, event) => {
 6  if(event.reset) {
 7   return {
 8     apple: '',
 9     count: 0,
10     name: '',
11     'gift-wrap': false,
12   }
13 }
14  return {
15    ...state,
16    [event.name]: event.value
17  }
18}
19
20function App() {
21  const [formData, setFormData] = useReducer(formReducer, {
22    count: 100
23  });
24  const [submitting, setSubmitting] = useState(false);
25
26  const handleSubmit = event => {
27    event.preventDefault();
28    setSubmitting(true);
29
30    setTimeout(() => {
31      setSubmitting(false);
32      setFormData({
33       reset: true
34     })
35    }, 3000);
36  }
37
38...

保存文件. 当您这样做时,浏览器将重新加载并在提交时清除表单。

Save the form and then clear the data

在此步骤中,您通过动态设置检查属性将未受控制的组件转换为受控制的组件,您还了解如何通过设置默认状态来补充数据,以及如何通过更新表单缩减器来清除数据以返回默认值。

在下一步中,您将动态设置表单组件属性,并在表单提交时禁用表单。

步骤 4 — 动态更新表单属性

在此步骤中,您将动态更新表单元素的属性. 您将根据之前的选择设置属性,并在提交时禁用表单,以防止随机多次提交。

目前,每个组件都是静态的. 它们不会随着表单的变化而改变. 在大多数应用程序中,表单是动态的. 字段将根据以前的数据而改变. 它们将验证并显示错误。

与大多数 React 组件一样,您可以在组件上动态设置属性和属性,这些属性会随着数据的变化而重新渲染。

尝试将输入设置为禁用,直到另一个输入满足条件. 更新 gift wrapping 检查框以禁用,除非用户选择fuji选项。

App.js中,将禁用属性添加到检查框中,如果formData.applefuji,则将属性变为真实:

 1[label form-tutorial/src/components/App/App.js]
 2...
 3        <fieldset>
 4          <label>
 5            <p>Apples</p>
 6            <select name="apple" onChange={handleChange} value={formData.apple || ''}>
 7                <option value="">--Please choose an option--</option>
 8                <option value="fuji">Fuji</option>
 9                <option value="jonathan">Jonathan</option>
10                <option value="honey-crisp">Honey Crisp</option>
11            </select>
12          </label>
13          <label>
14            <p>Count</p>
15            <input type="number" name="count" onChange={handleChange} step="1" value={formData.count || ''}/>
16          </label>
17          <label>
18            <p>Gift Wrap</p>
19            <input
20             checked={formData['gift-wrap'] || false}
21             disabled={formData.apple !== 'fuji'}
22             name="gift-wrap"
23             onChange={handleChange}
24             type="checkbox"
25            />
26          </label>
27        </fieldset>
28        <button type="submit">Submit</button>
29      </form>
30    </div>
31  )
32}
33
34export default App;

保存文件. 当您这样做时,浏览器将重新加载,默认情况下,检查框将被禁用:

Gift wrap is disabled

如果您选择 Fuji 的苹果类型,该元素将被启用:

Gift wrap is enabled

除了更改单个组件的属性外,还可以通过更新字段组件来更改整个组件。

例如,您可以在表单正在主动提交时禁用表单,从而防止重复提交,并防止用户在处理提交函数完全解决之前更改字段。

在每个<fieldset>元素和<button>元素中添加 disabled={submitting}:

 1[label form-tutorial/src/components/App/App.js]
 2...
 3      <form onSubmit={handleSubmit}>
 4        <fieldset disabled={submitting}>
 5          <label>
 6            <p>Name</p>
 7            <input name="name" onChange={handleChange} value={formData.name || ''}/>
 8          </label>
 9        </fieldset>
10        <fieldset disabled={submitting}>
11          <label>
12            <p>Apples</p>
13            <select name="apple" onChange={handleChange} value={formData.apple || ''}>
14                <option value="">--Please choose an option--</option>
15                <option value="fuji">Fuji</option>
16                <option value="jonathan">Jonathan</option>
17                <option value="honey-crisp">Honey Crisp</option>
18            </select>
19          </label>
20          <label>
21            <p>Count</p>
22            <input type="number" name="count" onChange={handleChange} step="1" value={formData.count || ''}/>
23          </label>
24          <label>
25            <p>Gift Wrap</p>
26            <input
27              checked={formData['gift-wrap'] || false}
28              disabled={formData.apple !== 'fuji'}
29              name="gift-wrap"
30              onChange={handleChange}
31              type="checkbox"
32            />
33          </label>
34        </fieldset>
35        <button type="submit" disabled={submitting}>Submit</button>
36      </form>
37    </div>
38  )
39}
40
41export default App;

保存文件,浏览器将更新. 当您提交表单时,这些字段将被禁用,直到提交函数解决:

Disable form elements when submitting

您可以更新输入组件上的任何属性,如果需要更改数字输入的最大值,或者如果需要添加动态的模式属性进行验证,则有帮助。

在此步骤中,您将动态设置表单组件的属性. 您添加了一个属性以动态启用或禁用基于来自其他组件的输入的组件,并使用<fieldset>组件禁用了整个部分。

结论

形式是丰富网络应用的关键. 在React中,对连接和控制形式和元素有不同的选项. 与其他组件一样,您可以动态地更新属性,包括"值"输入元素. 不受控制的组件最有利于简单化,但可能不适合某一组件需要清理或预先用数据填充的情况。 被控制的组件为您提供了更多的更新数据的机会,但可以添加又一层次的抽象,这可能导致无意的bug或再起.

无论您的方法如何,React 都为您提供动态更新和调整表单的能力,以满足您的应用程序和用户的需求。

如果您想阅读更多 React 教程,请查看我们的 React 主题页面,或返回 如何编码在 React.js 系列页面

Published At
Categories with 技术
comments powered by Disqus