Using Forms in React
在 React 中使用表单

By Dave Ceddia

No matter what kind of app you’re writing, there’s a good chance you need at least one form.
无论您正在编写哪种类型的应用程序,您很可能至少需要一种表单。

Forms in React are often a pain, filled with verbose and boilerplate-y code.
React 中的表单通常很痛苦,充满了冗长和样板代码。

Let’s look at how to make forms in React with less pain.
让我们看看如何在 React 中更轻松地制作表单。

In this article we’ll be focusing on using plain React, with no libraries. You’ll learn how forms really work, so you can confidently build them yourself. And if later you choose to add a form library, you’ll know how they work under the hood.
在本文中,我们将重点关注使用不带库的普通 React。您将了解表单的实际工作原理,以便您可以自信地自己构建表单。如果稍后您选择添加表单库,您就会知道它们在幕后是如何工作的。

We’re going to cover:
我们将介绍:

  • How to create React forms without installing any libraries
    如何在不安装任何库的情况下创建 React 表单
  • The two styles of inputs in React forms
    React 表单中的两种输入样式
  • When to use Controlled vs. Uncontrolled inputs
    何时使用受控输入与非受控输入
  • An easy way to get values out of uncontrolled inputs
    从不受控制的输入中获取值的简单方法

How to Create Forms with Plain React
如何使用普通 React 创建表单

Let’s dive right in. We’re going to build a simple contact form. Here’s the first iteration, a standalone component called ContactForm that renders a form:
让我们开始吧。我们将构建一个简单的联系表单。这是第一次迭代,一个名为 ContactForm 的独立组件,它呈现一个表单:

function ContactForm() {
  return (
    <form>
      <div>
        <label htmlFor="name">Name</label>
        <input id="name" type="text" />
      </div>
      <div>
        <label htmlFor="email">Email</label>
        <input id="email" type="email" />
      </div>
      <div>
        <label htmlFor="message">Message</label>
        <textarea id="message" />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

You don’t need to install a library to do any of this. React has built-in support for forms, because HTML and the DOM have built-in support for forms. At the end of the day, React is rendering DOM nodes.
您不需要安装库来执行这些操作。 React 内置了对表单的支持,因为 HTML 和 DOM 内置了对表单的支持。归根结底,React 正在渲染 DOM 节点。

In fact, for small forms, you probably don’t need a form library at all. Something like Formik or react-hook-form is overkill if all you need is a simple form.
事实上,对于小型表单,您可能根本不需要表单库。如果你只需要一个简单的形式,像 Formik 或 React-hook-form 这样的东西就太过分了。

There’s no state in here yet, and we’re not responding to form submission, but this component will already render a form you can interact with. (If you submit it, the page will reload, because submission is still being handled in the default way by the browser)
这里还没有状态,我们也没有响应表单提交,但该组件已经渲染了一个可以与之交互的表单。 (如果提交,页面将重新加载,因为浏览器仍然以默认方式处理提交)

React Forms vs. HTML Forms
React 表单与 HTML 表单

If you’ve worked with forms in plain HTML, a lot of this will probably seem familiar.
如果您使用过纯 HTML 表单,那么其中的很多内容可能看起来很熟悉。

There’s a form tag, and labels for the inputs, same as you’d write in HTML.
有一个 form 标记, label 代表 input ,与您在 HTML 中编写的相同。

Each label has an htmlFor prop that matches the id on its corresponding input. (That’s one difference: in HTML, the label attribute would be for. React uses htmlFor instead.)
每个标签都有一个 htmlFor 属性,与其相应输入上的 id 匹配。 (这是一个区别:在 HTML 中,标签属性将是 for 。React 使用 htmlFor 来代替。)

If you haven’t done much with plain HTML, just know that React didn’t make this stuff up! The things React does are pretty limited, and the way forms work is borrowed from HTML and the DOM.
如果你还没有对纯 HTML 做过太多工作,那么就知道 React 并不是编造出这些东西的! React 所做的事情非常有限,而且表单的工作方式是从 HTML 和 DOM 借用的。

Two Kinds of Inputs: Controlled vs. Uncontrolled
两种输入:受控与不受控

Inputs in React can be one of two types: controlled or uncontrolled.
React 中的输入可以是两种类型之一:受控或不受控。

An uncontrolled input is the simpler of the two. It’s the closest to a plain HTML input. React puts it on the page, and the browser keeps track of the rest. When you need to access the input’s value, React provides a way to do that. Uncontrolled inputs require less code, but make it harder to do certain things.
不受控制的输入是两者中较简单的一个。它最接近纯 HTML 输入。 React 将其放在页面上,浏览器跟踪其余部分。当您需要访问输入的值时,React 提供了一种方法来做到这一点。不受控制的输入需要更少的代码,但会使执行某些操作变得更加困难。

With a controlled input, YOU explicitly control the value that the input displays. You have to write code to respond to keypresses, store the current value somewhere, and pass that value back to the input to be displayed. It’s a feedback loop with your code in the middle. It’s more manual work to wire these up, but they offer the most control.
通过受控输入,您可以明确控制输入显示的值。您必须编写代码来响应按键,将当前值存储在某处,并将该值传递回要显示的输入。这是一个反馈循环,中间有你的代码。连接这些需要更多的手动工作,但它们提供了最多的控制。

Let’s look at these two styles in practice, applied to our contact form.
让我们看看这两种样式在实践中的应用,并应用于我们的联系表单。

Controlled Inputs 受控输入

With a controlled input, you write the code to manage the value explicitly.
通过受控输入,您可以编写代码来显式管理该值。

You’ll need to create state to hold it, update that state when the value changes, and explicitly tell the input what value to display.
您需要创建状态来保存它,在值更改时更新该状态,并明确告诉输入要显示什么值。

To update our contact form to use controlled inputs, we’ll need to add a few things, highlighted here:
要更新我们的联系表单以使用受控输入,我们需要添加一些内容,此处突出显示:

function ContactForm() {
  const [name, setName] = React.useState('');
  const [email, setEmail] = React.useState('');
  const [message, setMessage] = React.useState('');

  function handleSubmit(event) {
    event.preventDefault();
    console.log('name:', name);
    console.log('email:', email);
    console.log('message:', message);
  }

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">Name</label>
        <input
          id="name"
          type="text"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
      </div>
      <div>
        <label htmlFor="email">Email</label>
        <input
          id="email"
          type="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
      </div>
      <div>
        <label htmlFor="message">Message</label>
        <textarea
          id="message"
          value={message}
          onChange={(e) => setMessage(e.target.value)}
        />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

We’ve added 3 calls to useState to create 3 variables to hold the inputs’ values. They’re initially empty, ''.
我们添加了 3 个对 useState 的调用,以创建 3 个变量来保存输入的值。它们最初是空的, ''

Each input has gained a couple new props, too.
每个 input 也获得了一些新道具。

  • value tells the input what to display. Here, we’re passing the value from the corresponding state variable.
    value 告诉输入要显示什么。在这里,我们传递相应状态变量的值。
  • onChange is a function, and gets called when the user changes the input. It receives the event (commonly called e or event, but you can name it anything), and we take the input’s current value (e.target.value) and save it into state.
    onChange 是一个函数,当用户更改输入时被调用。它接收事件(通常称为 eevent ,但您可以将其命名为任何名称),然后我们获取输入的当前值 ( e.target.value ) 并将其保存到状态中。

Notice how manual this is. With every keypress, our onChange gets called, and we explicitly setWhatever, which re-renders the whole ContactForm with the new value.
请注意这是多么手动。每次按键时,我们的 onChange 都会被调用,并且我们显式地 setWhatever ,这会使用新值重新呈现整个 ContactForm。

This means that with every keypress, the component will re-render the whole form.
这意味着每次按键时,组件都会重新呈现整个表单。

For small forms this is fine. Really, it’s fine. Renders are fast. Rendering 3 or 5 or 10 inputs with every keypress is not going to perceptibly slow down the app.
对于小形式来说这很好。真的,没关系。渲染速度很快。每次按键渲染 3、5 或 10 个输入不会明显减慢应用程序的速度。

If you have a form with tons of inputs though, this re-rendering might start to matter, especially on slower devices. At this point you might need to look into optimizations, in order to limit the re-renders to only the inputs that changed.
如果您的表单包含大量输入,则这种重新渲染可能会开始变得重要,尤其是在速度较慢的设备上。此时,您可能需要研究优化,以便将重新渲染限制为仅更改的输入。

Or, consider how you could streamline the form so there are fewer inputs shown at once. If React isn’t happy about re-rendering 100 inputs on every keypress, I’d imagine your users aren’t very happy with seeing 100 inputs on a page either 😂
或者,考虑如何简化表单,以便一次显示的输入更少。如果 React 不愿意在每次按键时重新渲染 100 个输入,我想你的用户也不会很高兴在页面上看到 100 个输入😂

Alternatively… 或者…

Uncontrolled Inputs 不受控制的输入

If you do nothing beyond dropping an <input> in your render function, that input will be uncontrolled. You tell React to render the input, and the browser does the rest.
如果除了在渲染函数中删除 <input> 之外什么都不做,那么该输入将不受控制。你告诉 React 渲染输入,浏览器完成剩下的工作。

Uncontrolled inputs manage their own value. Just like with a plain HTML form, the value is kept in the input’s DOM node. No need to manually track it.
不受控制的投入管理着自己的价值。就像纯 HTML 表单一样,该值保存在输入的 DOM 节点中。无需手动跟踪它。

In the first code sample on this page, all the inputs were uncontrolled, because we weren’t passing the value prop that would tell them what value to display.
在此页面上的第一个代码示例中,所有输入都不受控制,因为我们没有传递 value 属性来告诉他们要显示什么值。

But if we’re not actively tracking the value… how can we tell what the value is?
但如果我们不主动跟踪价值……我们如何知道价值是什么?

Here’s where “refs” come in.
这就是“参考文献”的用武之地。

What is a “ref”?
什么是“参考”?

React takes your JSX and constructs the actual DOM, which the browser displays. Refs tie these two representations together, letting your React component get access to the DOM nodes that represent it.
React 获取您的 JSX 并构建浏览器显示的实际 DOM。 Refs 将这两种表示形式结合在一起,让您的 React 组件可以访问表示它的 DOM 节点。

A ref holds a reference to a DOM node.
ref 保存对 DOM 节点的引用。

Here’s why that matters: The JSX you write is merely a description of the page you want to create. What you really need is the underlying DOM input, so that you can pull out the value.
这就是为什么这很重要:您编写的 JSX 只是您要创建的页面的描述。您真正需要的是底层 DOM input ,以便您可以提取该值。

So, to get the value from an uncontrolled input, you need a reference to it, which we get by assigning a ref prop. Then you can read out the value when the form is submitted (or really, whenever you want!).
因此,要从不受控制的输入中获取值,您需要对其进行引用,我们通过分配 ref 属性来获取该引用。然后,您可以在提交表单时读出该值(或者实际上,只要您愿意!)。

Let’s add refs to our contact form inputs, building upon the “bare form” example from earlier:
让我们在之前的“裸表单”示例的基础上,将引用添加到联系表单输入中:

function ContactForm() {
  const nameRef = React.useRef();
  const emailRef = React.useRef();
  const messageRef = React.useRef();

  return (
    <form>
      <div>
        <label htmlFor="name">Name</label>
        <input
          id="name"
          type="text"
          ref={nameRef}
        />
      </div>
      <div>
        <label htmlFor="email">Email</label>
        <input
          id="email"
          type="email"
          ref={emailRef}
        />
      </div>
      <div>
        <label htmlFor="message">Message</label>
        <textarea
          id="message"
          ref={messageRef}
        />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

We did a couple things here:
我们在这里做了几件事:

  • created 3 refs with the useRef hook
    使用 useRef 钩子创建了 3 个引用
  • bound the refs to the inputs with the ref prop
    使用 ref 属性将引用绑定到输入

When the component is first rendered, React will set up the refs. nameRef.current will then refer to the name input’s DOM node, emailRef.current will refer to the email input, and so on.
当组件首次渲染时,React 将设置引用。 nameRef.current 将引用 name 输入的 DOM 节点, emailRef.current 将引用电子邮件输入,依此类推。

These refs hold the same values as the ones you’d get if you ran a document.querySelector('input[id=name]') in your browser console. It’s the browser’s raw input node; React is just passing it back to you.
这些引用的值与在浏览器控制台中运行 document.querySelector('input[id=name]') 时得到的值相同。它是浏览器的原始输入节点; React 只是把它传回给你。

The last piece of the puzzle is how to get the values out of the inputs.
最后一个难题是如何从输入中获取值。

Uncontrolled inputs are the best choice when you only need to do something with the value at a specific time, such as when the form is submitted. (If you need to inspect/validate/transform the value on every keypress, use a controlled input)
当您只需要在特定时间(例如提交表单时)对值执行某些操作时,不受控制的输入是最佳选择。 (如果您需要检查/验证/转换每次按键时的值,请使用受控输入)

We can create a function to handle form submission, and print out the values:
我们可以创建一个函数来处理表单提交,并打印出值:

function ContactForm() {
  const nameRef = React.useRef();
  const emailRef = React.useRef();
  const messageRef = React.useRef();

  function handleSubmit(event) {
    event.preventDefault();
    console.log('name:', nameRef.current.value);
    console.log('email:', emailRef.current.value);
    console.log('message:', messageRef.current.value);
  }

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="name">Name</label>
        <input
          id="name"
          type="text"
          ref={nameRef}
        />
      </div>
      <div>
        <label htmlFor="email">Email</label>
        <input
          id="email"
          type="email"
          ref={emailRef}
        />
      </div>
      <div>
        <label htmlFor="message">Message</label>
        <textarea
          id="message"
          ref={messageRef}
        />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

Your handleSubmit function can then do whatever you need with those values: validate them, asynchronously POST them to a server, etc.
然后,您的 handleSubmit 函数可以对这些值执行您需要的任何操作:验证它们、将它们异步 POST 到服务器等。

Notice we’re calling event.preventDefault() at the top. Without this, submitting the form would refresh the page.
请注意,我们在顶部调用 event.preventDefault() 。如果没有这个,提交表单将刷新页面。

Controlled vs. Uncontrolled: Which to Use?
受控与非受控:使用哪个?

Let’t go over some pros and cons of each style of input so you can decide which you want to use.
我们先不讨论每种输入方式的优缺点,以便您决定要使用哪种方式。

(You might’ve heard that controlled inputs are a “best practice” which of course would imply uncontrolled inputs are NOT! 😱 I’ll address this near the end.)
(您可能听说过受控输入是“最佳实践”,这当然意味着不受控输入不是!😱我将在接近尾声时解决这个问题。)

When and Why to Use Controlled Inputs
何时以及为何使用受控输入

Of the two styles, controlled inputs are the more “React-y way” of doing things, where UI reflects state. By changing the state, you change the UI. If you don’t change the state, the UI stays the same. You don’t meddle with the underlying input in an imperative, mutable way.
在这两种风格中,受控输入是更“React-y 方式”的做事方式,其中 UI 反映状态。通过更改状态,您可以更改 UI。如果不更改状态,用户界面将保持不变。您不会以命令式、可变的方式干预底层输入。

This makes controlled inputs perfect for things like:
这使得受控输入非常适合以下情况:

  • Instantly validating the form on every keypress: useful if you want to keep the Submit button disabled until everything is valid, for instance.
    每次按键时立即验证表单:例如,如果您希望在一切都有效之前保持“提交”按钮处于禁用状态,则很有用。
  • Handling formatted input, like a credit card number field, or preventing certain characters from being typed.
    处理格式化输入,例如信用卡号码字段,或阻止键入某些字符。
  • Keeping multiple inputs in sync with each other when they’re based on the same data
    当多个输入基于相同数据时保持彼此同步

The buck stops with you, dear developer. Want to ignore some weird character the user typed? Easy, just strip it out.
亲爱的开发者,责任由你承担。想要忽略用户输入的一些奇怪字符?很简单,把它去掉就可以了。

function EmailField() {
  const [email, setEmail] = useState('');

  const handleChange = e => {
    // no exclamations allowed!
    setEmail(e.target.value.replace(/!/g, ''));
  }

  return (
    <div>
      <label htmlFor="email">Email address</label>
      <input
        id="email"
        value={email}
        onChange={handleChange}
      />
    </div>
  );
}

There are plenty of use cases where you want to react to every keypress and handle it somehow. Controlled inputs are good for that.
在很多用例中,您希望对每个按键做出反应并以某种方式处理它。受控输入对此很有好处。

But there are some downsides.
但也有一些缺点。

Controlled Inputs are More Complex
受控输入更加复杂

As we’ve already seen, you have to manually manage the value of the input, which means you need (a) state to hold it and (b) a change handler function, and you need those for every input.
正如我们已经看到的,您必须手动管理输入的值,这意味着您需要 (a) 状态来保存它,以及 (b) 更改处理函数,并且每个输入都需要这些函数。

You can work around part of this problem by combining the inputs into one state object:
您可以通过将输入组合到一个状态对象中来部分解决此问题:

function MultipleInputs() {
  const [values, setValues] = useState({
    email: '',
    name: ''
  });

  const handleChange = e => {
    setValues(oldValues => ({
      ...oldValues,
      [e.target.name]: e.target.value
    }));
  }

  return (
    <>
      <div>
        <label htmlFor="email">Email address</label>
        <input
          id="email"
          name="email"
          value={values.email}
          onChange={handleChange}
        />
      </div>
      <div>
        <label htmlFor="name">Full Name</label>
        <input
          id="name"
          name="name"
          value={values.name}
          onChange={handleChange}
        />
      </div>
    </>
  );
}

It’s nicer, but it’s still code you need to write.
它更好,但它仍然是您需要编写的代码。

Boilerplate like this is one of the reasons React form libraries are so popular – but again, if you have 2 or 3 inputs on a page, I would argue that saving a few lines of tedium is not worth adding a form library.
像这样的样板是 React 表单库如此受欢迎的原因之一 - 但同样,如果页面上有 2 或 3 个输入,我认为节省几行单调乏味的内容不值得添加表单库。

Controlled Inputs Re-render on Every Keypress
每次按键时重新渲染受控输入

Every time you press a key, React calls the function in theonChange prop, which sets the state. Setting the state causes the component and its children to re-render (unless they’re already optimized with React.memo or PureComponent).
每次按下一个键,React 都会调用 onChange 属性中的函数来设置状态。设置状态会导致组件及其子组件重新渲染(除非它们已经使用 React.memoPureComponent 进行了优化)。

This is mostly fine. Renders are fast. For small-to-medium forms you probably won’t even notice. And it’s not that rendering a piddly little input is slow… but it can be a problem in aggregate.
这基本上没问题。渲染速度很快。对于中小型表格,您可能甚至不会注意到。这并不是说渲染一个小小的 input 很慢……但总的来说,这可能是一个问题。

As the number of inputs grows – or if your form has child components that are expensive to render – keypresses might start to feel perceptibly laggy. This threshold is even lower on mobile devices.
随着输入数量的增加 - 或者如果您的表单具有渲染成本高昂的子组件 - 按键可能会开始感觉明显滞后。在移动设备上这个门槛甚至更低。

It can become a problem of death-by-a-thousand-cuts.
这可能会成为一个千刀万剐的问题。

If you start to suspect this problem in your app, fire up the Profiler in the React Developer Tools and take a measurement while you bash on some keys. It’ll tell you which components are slowing things down.
如果您开始怀疑您的应用程序存在此问题,请启动 React Developer Tools 中的 Profiler,并在敲击某些键时进行测量。它会告诉您哪些组件正在减慢速度。

Uncontrolled Inputs Don’t Re-render
不受控制的输入不会重新渲染

A big point in favor of using uncontrolled inputs is that the browser takes care of the whole thing.
支持使用不受控制的输入的一个重要观点是浏览器会处理整个事情。

You don’t need to update state, which means you don’t need to re-render. Every keypress bypasses React and goes straight to the browser.
你不需要更新状态,这意味着你不需要重新渲染。每次按键都会绕过 React 并直接进入浏览器。

Typing the letter 'a' into a form with 300 inputs will re-render exactly zero times, which means React can pretty much sit back and do nothing. Doing nothing is very performant.
在包含 300 个输入的表单中输入字母 'a' 将会重新渲染零次,这意味着 React 几乎可以坐下来什么也不做。什么都不做是非常高效的。

Uncontrolled Inputs Can Have Even Less Boilerplate!
不受控制的输入可以有更少的样板!

Earlier we looked at how to create references to inputs using useRef and pass them as the ref prop.
之前我们了解了如何使用 useRef 创建对输入的引用并将它们作为 ref 属性传递。

You can actually go a step further and remove the refs entirely, by taking advantage of the fact that a form knows about its own inputs.
实际上,您可以更进一步,利用 form 了解其自己的输入这一事实,完全删除引用。

function NoRefsForm() {
  const handleSubmit = e => {
    e.preventDefault();
    const form = e.target;
    console.log('email', form.email, form.elements.email);
    console.log('name', form.name, form.elements.name);
  }

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="email">Email address</label>
        <input
          id="email"
          name="email"
        />
      </div>
      <div>
        <label htmlFor="name">Full Name</label>
        <input
          id="name"
          name="name"
        />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

The inputs are properties on the form itself, named by their id AND their name. Yep, both.
输入是 form 本身的属性,由 idname 命名。是的,两者都有。

They’re also available at form.elements. Check it out:
您也可以通过 form.elements 获取它们。一探究竟:

function App() {
  const handleSubmit = (e) => {
    e.preventDefault();
    const form = e.target;
    console.log(
      form.email,
      form.elements.email,
      form.userEmail,
      form.elements.userEmail);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="userEmail">Email address</label>
        <input id="userEmail" name="email" />
      </div>
      <button type="submit">Submit</button>
    </form>
  );
}

This prints the same input 4 times:
这会打印相同的输入 4 次:

<input id="userEmail" name="email"></input>
<input id="userEmail" name="email"></input>
<input id="userEmail" name="email"></input>
<input id="userEmail" name="email"></input>

So we can leave off the redundant name prop from the input, if we don’t need it for anything else.
因此,如果我们不需要它做其他任何事情,我们可以从输入中删除多余的 name 属性。

(we need to keep the id because the label’s htmlFor refers to that)
(我们需要保留 id ,因为标签的 htmlFor 指的是它)

The form.elements array is useful if you need to loop over every input, like if you have a bunch of dynamically-generated ones or something.
如果您需要循环每个输入,例如如果您有一堆动态生成的输入或其他内容,则 form.elements 数组非常有用。

Accessible Form Labels 无障碍表单标签

Every input should have a label. Label-less inputs make trouble for screenreaders, which makes trouble for humans… and placeholder text unfortunately doesn’t cut it.
每个输入都应该有一个标签。无标签输入给屏幕阅读器带来了麻烦,这给人类带来了麻烦……不幸的是,占位符文本并不能解决这个问题。

The two ways to do labels are:
制作标签的两种方法是:

Label Next To Input (2 sibling elements)
输入旁边的标签(2 个兄弟元素)

Give the input an id and the label an htmlFor that matches, and put the elements side-by-side. Order doesn’t matter, as long as the identifiers match up.
为输入指定一个匹配的 id ,为标签指定一个匹配的 htmlFor ,然后将元素并排放置。只要标识符匹配,顺序并不重要。

<label htmlFor="wat">Email address</label>
<input id="wat" name="email" />

Input Inside Label
输入内标签

If you wrap the input in a label, you don’t need the id and the htmlFor. You’ll want a way to refer to the input though, so give it an id or a name.
如果将 input 包装在 label 中,则不需要 idhtmlFor 。不过,您需要一种引用输入的方法,因此给它一个 idname

<label>
  Email Address
  <input type="email" name="email" />
</label>

If you need more control over the style of the text, you can wrap it in a span.
如果您需要对文本样式进行更多控制,可以将其包装在 span 中。

Visually Hidden, But Still Accessible
视觉上隐藏,但仍然可以访问

You can hide the label with CSS if you need to.
如果需要,您可以使用 CSS 隐藏标签。

Most of the big CSS frameworks have a screenreader-only class, often sr-only, that will hide the label in a way that screenreaders will still be able to read it. Here’s a generic sr-only implementation.
大多数大型 CSS 框架都有一个仅限屏幕阅读器的类,通常是 sr-only ,它将以屏幕阅读器仍然能够阅读的方式隐藏标签。这是一个通用的仅 sr 实现。

One nice thing about labels is, once you have them associated correctly, the browser will translate clicks on the label as clicks on the input. This is most noticeable with radio buttons – when the label is set up right, clicking the text will select the radio, but otherwise, it’ll frustratingly ignore you.
标签的一个好处是,一旦将它们正确关联,浏览器就会将标签上的点击转换为输入上的点击。这在单选按钮中最为明显——当标签设置正确时,单击文本将选择单选按钮,但否则,它会令人沮丧地忽略您。

For more specifics see Lindsey’s post An Introduction to Accessible Labeling
有关更多详细信息,请参阅 Lindsey 的文章《无障碍标签简介》

Reduce Form Boilerplate With Small Components
使用小组件减少样板形式

So you’ve added your labels, but these inputs are getting longer and more repetitive…
所以您已经添加了标签,但这些输入变得越来越长且越来越重复......

<div>
  <label htmlFor="email">Email Address</label>
  <input name="email" id="email">
</div>

You can easily move this to a component, though!
不过,您可以轻松地将其移动到组件中!

function Input({ name, label }) {
  return (
    <div>
      <label htmlFor={name}>{label}</label>
      <input name={name} id={name}>
    </div>
  );
}

Now every input is simple again.
现在每个输入都变得简单了。

<Input name="email" label="Email Address"/>

And if you’re using uncontrolled inputs, you can still use the trick of reading the values off the form, no refs or state required.
如果您使用不受控制的输入,您仍然可以使用从表单中读取值的技巧,无需引用或状态。

Is It a Best Practice to Use Controlled Inputs?
使用受控输入是最佳实践吗?

As of this writing, the React docs have a recommendation about inputs:
截至撰写本文时,React 文档对输入提出了建议:

In most cases, we recommend using controlled components to implement forms. In a controlled component, form data is handled by a React component. The alternative is uncontrolled components, where form data is handled by the DOM itself.
在大多数情况下,我们建议使用受控组件来实现表单。在受控组件中,表单数据由 React 组件处理。另一种选择是不受控制的组件,其中表单数据由 DOM 本身处理。

They go on to say that uncontrolled inputs are the easy way out:
他们接着说,不受控制的输入是最简单的出路:

It can also be slightly less code if you want to be quick and dirty. Otherwise, you should usually use controlled components.
如果你想快速而肮脏,它也可以稍微少一些代码。否则,您通常应该使用受控组件。

The docs don’t exactly explain their reasoning, but my hunch is their recommendation stems from the fact that controlled inputs closely follow the state-driven approach, which is React’s whole reason for existing. Uncontrolled inputs are then treated as an “escape hatch” for when the state-driven approach won’t work for whatever reason.
这些文档并没有准确解释他们的推理,但我的预感是他们的建议源于这样一个事实:受控输入紧密遵循状态驱动方法,这就是 React 存在的全部原因。当状态驱动方法因某种原因无法工作时,不受控制的输入将被视为“逃生舱”。

I agreed with this line of thinking for a while, but I’m starting to have second thoughts.
我有一段时间同意这种想法,但我开始重新考虑。

I’m coming around to the idea that uncontrolled inputs might actually be the better default.
我开始认为不受控制的输入实际上可能是更好的默认设置。

So this might get me some flak, but I’m going to say it anyway:
所以这可能会让我受到一些批评,但我还是要说:

If uncontrolled inputs work for your case, use ‘em! They’re easier, and faster.
如果不受控制的输入适合您的情况,请使用它们!它们更容易、更快。

I don’t think I’m alone in this. The popular react-hook-form library uses uncontrolled inputs under the hood to make things fast. And I’ve seen some React thought leaders questioning why we don’t use uncontrolled inputs more often, too. Maybe it’s time to give it some thought!
我认为我并不孤单。流行的react-hook-form库在底层使用不受控制的输入来使事情变得更快。我还看到一些 React 思想领袖质疑为什么我们不更频繁地使用不受控制的输入。也许是时候考虑一​​下了!

Are Uncontrolled Inputs an Antipattern?
不受控制的输入是反模式吗?

Uncontrolled inputs are a feature like any other, and they come with some tradeoffs (which we covered above), but they’re not an antipattern.
不受控制的输入是一个与其他任何功能一样的功能,它们需要一些权衡(我们在上面介绍过),但它们不是反模式。

I tend to reserve the word “antipattern” for techniques that will come back to bite you later on. React has antipatterns like
我倾向于将“反模式”这个词保留给那些稍后会反噬你的技术。 React 有反模式,例如

  • mutating state instead of using immutability
    改变状态而不是使用不变性
  • duplicating values from props into state, and trying to keep them in sync
    将属性中的值复制到状态中,并尝试使它们保持同步
  • performing side effects in the body of a component function, instead of in a useEffect hook
    在组件函数体中而不是在 useEffect 挂钩中执行副作用

These are things that sometimes appear to work just fine, but are ultimately the wrong way to do it, and will cause bugs down the road.
这些东西有时看起来工作得很好,但最终是错误的方法,并且会导致错误。

Uncontrolled inputs are a bit unconventional today, but using them is not “doing it wrong”. It’s a matter of picking the right tool for the job. If you go in knowing their limitations, and knowing your use case, then you can be pretty confident in your choice.
如今,不受控制的输入有点不合常规,但使用它们并不意味着“做错了”。这是为工作选择正确工具的问题。如果您了解它们的局限性并了解您的用例,那么您可以对自己的选择非常有信心。

Go Make Forms! 去制作表格吧!

I hope this overview of forms in React was helpful! There’s lots more I could cover but honestly this was too long already 😅 If you want to see more on forms, let me know in the comments.
我希望 React 中的表单概述对您有所帮助!我还有很多内容可以介绍,但说实话,这已经太长了 😅 如果您想了解有关表单的更多信息,请在评论中告诉我。

Learning React can be a struggle — so many libraries and tools!
学习 React 可能会很困难——有这么多的库和工具!

My advice? Ignore all of them :)
我的建议?忽略所有这些:)

For a step-by-step approach, check out my Pure React workshop.
如需了解分步方法,请查看我的 Pure React 研讨会。

Pure React plant

Learn to think in React
学会用 React 思考

  • 90+ screencast lessons 90 多节截屏视频课程
  • Full transcripts and closed captions
    完整的文字记录和隐藏式字幕
  • All the code from the lessons
    课程中的所有代码
  • Developer interviews
Start learning Pure React now

Dave Ceddia’s Pure React is a work of enormous clarity and depth. Hats off. I'm a React trainer in London and would thoroughly recommend this to all front end devs wanting to upskill or consolidate.

Alan Lavender
Alan Lavender
@lavenderlens