如何在 React 中构建表单

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

介绍

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

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

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

先决条件

第 1 步 – 使用 JSX 创建一个基本表单

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

首先,打开App.js

  • nano src/components/App/App.js

您将构建一个用于购买苹果的表单。创建<div>具有className<wrapper>然后通过添加以下突出显示的代码添加一个<h1>带有文本“How About Them Apples”标签和一个空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>标签包围元素通过<input><label>标签包装元素,您可以通过将标签与输入相关联来帮助屏幕阅读器。这将增加您的应用程序的可访问性。

最后,<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,请在setTimeout函数中添加一个handleSubmit函数。这将创建一个异步操作,在完成之前等待一段时间,其行为类似于对外部数据的请求。然后使用useStateHook 创建一个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;

此外,你会提醒他们的形式通过显示时会显示HTML短消息提交用户submittingtrue

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

表单提交显示消息 3 秒

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

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

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

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

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

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

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

在 内部App.js,使用useReducerHook 创建一个formData对象和一个setFormData函数。对于reducer 函数,对象中拉出nameand通过在末尾添加and 的同时传播当前状态来更新the 这将创建一个状态对象,该对象保留当前状态,同时在更改时覆盖特定值:valueevent.targetstatenamevalue

表单教程/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;

制作减速器后,添加setFormDataonChange输入事件处理程序中。保存文件。当您这样做时,浏览器将重新加载。但是,如果您尝试输入输入,则会出现错误:

SyntheticEvent 出错

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

更新 reducer 函数以获取具有nameand属性的对象value然后创建一个调用的函数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,则映射数据的阵列的每个成员转换为在<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>元素并<select>为每个元素添加一个具有不同苹果品种元素<option>,一个<input>带有 atype="number"和 astep="1"以获得递增 1 的计数,以及一个<input>带有type="checkbox"礼物包装选项元素

对于每个元素,将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",不管项目是否被选中与否。value您需要使用checked属性而不是使用事件的

更新handleChange函数以查看是否event.target.typecheckbox如果是,则将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,
   })
 }
...

在此代码中,您使用?三元运算符来创建条件语句。

保存文件。浏览器刷新后,填写表单并点击提交。您会发现警报与表单中的数据相匹配:

提交正确数据的表单元素

在这一步中,您学习了如何创建不受控制的表单组件。您使用useReducerHook将表单数据保存到一个状态,并在不同的组件中重用该数据。您还添加了不同类型的表单组件并调整了您的功能以根据元素类型保存正确的数据。

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

第 3 步 – 使用受控组件更新表单数据

在此步骤中,您将使用受控组件动态设置和更新数据。您将为每个组件添加一个value 道具以设置或更新表单数据。您还将在提交时重置表单数据。

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

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

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

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

在这种形式中,您已经在存储数据,因此要转换组件,您将value使用formData状态中的数据更新prop 但是,有一个问题: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属性而不是设置值如果该属性为真,浏览器将显示该框为选中状态。使用 将初始checked属性设置为 false formData['gift-wrap'] || false

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

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

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

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

具有默认计数的表单

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

现在您拥有活动组件,您可以在提交时清除数据。为此,请在您的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直到另一个输入满足条件。更新礼品包装复选框以禁用,除非用户选择该fuji选项。

在里面App.js,将disabled属性添加到复选框。如果是,formData.apple使属性为真fuji

表单教程/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>组件禁用了整个部分

结论

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 中编码”系列页面

觉得文章有用?

点个广告表达一下你的爱意吧 !😁