如何在React中构建表单

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Creative Commons 来接受捐赠。

介绍

表单是 React Web 应用程序的重要组成部分。 它们允许用户在从登录屏幕到结帐页面的组件中直接输入和提交数据。 由于大多数 React 应用程序是 单页应用程序 (SPA),或者加载单个页面并动态显示新数据的 Web 应用程序,因此您不会将信息直接从表单提交到服务器。 相反,您将在客户端捕获表单信息并使用附加的 JavaScript 代码发送或显示它。

React 表单提出了一个独特的挑战,因为您可以允许浏览器处理大多数表单元素并通过 React 更改事件 收集数据,或者您可以使用 React 通过设置和更新输入来完全控制元素直接取值。 第一种方法称为 不受控制的组件 ,因为 React 没有设置值。 第二种方法称为 控制的组件,因为 React 正在主动更新输入。

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

先决条件

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

在这一步中,您将使用 JSX 创建一个带有单个元素和一个提交按钮的空表单。 您将处理表单提交事件并将数据传递给另一个服务。 在这一步结束时,您将拥有一个基本表单,它将数据提交给 异步函数

首先,打开 App.js

nano src/components/App/App.js

您将构建一个用于购买苹果的表单。 创建一个[[how-to-build-a-website-with-html#how-to-use-a-<div>,-the-html-content-division-element|]]

className<wrapper> . 然后通过添加以下突出显示的代码来添加带有文本“How About Them Apples”的 <h1> 标记和一个空的 form 元素:

表单教程/src/components/App/App.js

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> 标记包围。 通过使用 <label> 标签包装 <input> 元素,您可以通过将标签与输入相关联来帮助屏幕阅读器。 这将增加您的应用程序的可访问性。

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

表单教程/src/components/App/App.js

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 添加到 .wrappermarginfieldset 以在元素之间留出一些空间:

表单教程/src/components/App/App.css

.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 以显示表单已提交:

表单教程/src/components/App/App.js

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,请在 handleSubmit 函数中添加 setTimeout 函数。 这将创建一个 异步操作 在完成之前等待一定的时间,其行为类似于对外部数据的请求。 然后使用 useState Hook 创建一个 submitting 变量和一个 setSubmitting 函数。 数据提交时调用setSubmitting(true),超时解决时调用setSubmitting(false)

表单教程/src/components/App/App.js

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;

此外,当 submittingtrue 时,您将通过在 HTML 中显示一条短消息来提醒用户他们的表单正在提交。

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

现在你有一个基本的表单来处理 React 组件中的提交事件。 您已使用 onSubmit 事件处理程序将其连接到 JSX,并且您正在使用 Hooks 在 handleSubmit 事件运行时 有条件地 显示警报。

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

第 2 步 — 使用不受控制的组件收集表单数据

在此步骤中,您将使用 非受控组件 收集表单数据。 不受控制的组件是没有 React 设置的 value 的组件。 您将连接到 onChange 事件来收集用户输入,而不是在组件上设置数据。 在构建组件时,您将了解 React 如何处理不同的输入类型以及如何创建可重用函数来将表单数据收集到单个 对象 中。

在此步骤结束时,您将能够使用不同的表单元素(包括下拉菜单和复选框)构建表单。 您还可以收集、提交和显示表单数据。

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


目前,您有一个可以提交信息的表单,但没有什么可提交的。 该表单有一个 <input> 元素,但您没有在组件的任何位置收集或存储数据。 为了能够在用户提交表单时存储和处理数据,您需要创建一种方法来 管理状态 。 然后,您需要使用事件处理程序连接到每个输入。

App.js 内部,使用 useReducer Hook 创建一个 formData 对象和一个 setFormData 函数。 对于 reducer 函数,从 event.target 对象中拉出 namevalue 并通过 扩展 来更新 state 的当前状态,同时在末尾添加 namevalue。 这将创建一个状态对象,该对象保留当前状态,同时在特定值更改时覆盖它们:

表单教程/src/components/App/App.js

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;

制作 reducer 后,将 setFormData 添加到输入的 onChange 事件处理程序中。 保存文件。 当你这样做时,浏览器将重新加载。 但是,如果您尝试输入输入,则会收到错误消息:

问题是 SyntheticEvent重用,不能传递给异步函数 。 换句话说,你不能直接传递事件。 要解决此问题,您需要在调用 reducer 函数之前提取所需的数据。

更新 reducer 函数以获取具有 namevalue 属性的对象。 然后创建一个名为 handleChange 的函数,该函数从 event.target 中提取数据并将对象传递给 setFormData。 最后,更新 onChange 事件处理程序以使用新函数:

表单教程/src/components/App/App.js

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 将数据转换为 array,然后在数据上使用 map 将数组的每个成员转换为具有名称和的 <li> 元素价值。 请务必使用 name 作为元素的 key 属性:

表单教程/src/components/App/App.js

...
  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> 元素并为每个 <option> 添加一个具有不同苹果品种的 <select> 元素,一个带有 type="number"<input> 和一个step="1" 获得以 1 为增量的计数,以及带有 type="checkbox"<input> 用于礼品包装选项。

对于每个元素,将 handleChange 函数添加到 onChange 事件处理程序:

表单教程/src/components/App/App.js

...
  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",无论该项目是否被选中。 您需要使用 checked 属性,而不是使用事件的 value

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

表单教程/src/components/App/App.js

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 prop 以设置或更新表单数据。 您还将在提交时重置表单数据。

在这一步结束时,您将能够使用 React 状态和道具动态控制表单数据。

使用不受控制的组件,您不必担心同步数据。 您的应用程序将始终保留最新的更改。 但是在许多情况下,您需要同时读取和写入输入组件。 为此,您需要组件的值是动态的。

在上一步中,您提交了一个表单。 但是表单提交成功后,表单仍然包含旧数据。 要从每个输入中删除数据,您需要将组件从非受控组件更改为受控组件。

受控组件类似于非受控组件,但 React 更新了 value 属性。 不利的一面是,如果您不小心并且没有正确更新 value 属性,组件将出现损坏并且似乎不会更新。

在这种形式中,您已经存储了数据,因此要转换组件,您将使用 formData 状态的数据更新 value 属性。 但是有一个问题:value 不能是 undefined。 如果您的值为 undefined,您将在控制台中收到错误消息。

由于您的初始状态是一个空对象,因此您需要将值设置为 formData 中的值或空字符串等默认值。 例如,名称的值为 formData.name ||

表单教程/src/components/App/App.js

...
  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 属性,而不是设置值。 如果该属性为真,浏览器将显示该框为选中状态。 使用 formData['gift-wrap'] || false 将初始 checked 属性设置为 false。

如果要预填表格,在 formData 状态下添加一些默认数据。 通过为 formState 设置默认值 { count: 100 } 来为 count 设置默认值。 您还可以在初始对象中设置默认值,但您需要在显示表单信息之前过滤掉虚假值:

表单教程/src/components/App/App.js

...

function App() {
  const [formData, setFormData] = useReducer(formReducer, {
   count: 100,
 });
  const [submitting, setSubmitting] = useState(false);
...

保存文件。 当您这样做时,浏览器将重新加载,您将看到带有默认数据的输入:

注意: value 属性与浏览器原生的 placeholder 属性不同。 placeholder 属性显示信息,但一旦用户进行更改就会消失; 它不存储在组件上。 您可以主动编辑 value,但 placeholder 只是用户的指南。


现在您有了活动组件,您可以在提交时清除数据。 为此,请在 formReducer 中添加一个新条件。 如果 event.reset 为真,则为每个表单元素返回一个具有空值的对象。 确保为每个输入添加一个值。 如果返回空对象或不完整对象,则组件不会更新,因为值为 undefined

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

表单教程/src/components/App/App.js

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);
  }

...

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

在此步骤中,您通过动态设置 valuechecked 属性将非受控组件转换为受控组件。 您还学习了如何通过设置默认状态来重新填充数据,以及如何通过更新表单缩减程序以返回默认值来清除数据。

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

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

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

目前,每个组件都是静态的。 它们不会随着形式的变化而变化。 在大多数应用程序中,表单是动态的。 字段将根据以前的数据发生变化。 他们将验证并显示错误。 当您填写其他组件时,它们可能会消失或扩展。

像大多数 React 组件一样,您可以动态设置组件的属性和属性,并且它们会随着数据的变化而重新渲染。

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

App.js 中,将 disabled 属性添加到复选框。 如果 formData.applefuji,则使属性为真:

表单教程/src/components/App/App.js

...
        <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> 元素:

表单教程/src/components/App/App.js

...
      <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> 组件禁用了整个部分。

结论

表单是富 Web 应用程序的关键。 在 React 中,您有不同的选项来连接和控制表单和元素。 与其他组件一样,您可以动态更新属性,包括 value 输入元素。 不受控制的组件最适合简单起见,但可能不适合需要清除组件或预先填充数据的情况。 受控组件为您提供了更多更新数据的机会,但可以添加另一个抽象级别,这可能会导致意外错误或重新渲染。

无论您采用哪种方法,React 都使您能够动态更新和调整表单以满足应用程序和用户的需求。

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