这是用户在 2024-11-25 11:00 为 https://buildui.com/posts/uncontrolled-vs-controlled-a-matter-of-perspective 保存的双语快照页面,由 沉浸式翻译 提供双语支持。了解如何保存?

Uncontrolled or controlled: A matter of perspective
无控制或控制:一个视角问题

Sam Selikoff

Sam Selikoff

You may have come across the terms uncontrolled component and controlled component in your React journey.
你可能已经在你的 React 之旅中遇到过“未受控组件”和“受控组件”这两个术语。

These terms are typically introduced by looking at the two ways native input elements can be used in React: as uncontrolled inputs, or as controlled inputs.
这些术语通常是通过观察原生 input 元素在 React 中的两种使用方式来引入的:作为非受控输入或作为受控输入。

An uncontrolled input is an input whose state lives entirely in the DOM:
一个未受控制的输入是指其状态完全存在于 DOM 中的输入:

function Page() {
  return <input name="search" />;
}

React doesn't know about this input's value. It doesn't set or update it, and it doesn't know when it changes. In this sense, this input's value is uncontrolled by React.
React 不知道这个输入值的值。它不设置或更新它,也不知道它何时改变。从这个意义上说,这个输入的值不受 React 控制。

A controlled input, on the other hand, delegates its state to React:
一个受控输入,另一方面,将其状态委托给 React:

function Page() {
  const [search, setSearch] = useState('');

  return (
   <> 
    <input
      value={search}
      onChange={(e) => setSearch(e.target.value)}
      name="search"
    />
    
    <button onClick={() => setSearch('')}>Clear</button>
   </>
  );
}

React fully controls this input's value. The initial value of the search state, as well as any subsequent updates, will always be reflected by the input. Importantly, not only will typing in the input update its value (thanks to the onChange handler), but anything else that updates the search state – like the Clear button – will also update the input.
React 完全控制此输入的值。 search 状态的初始值以及任何后续更新都将通过输入反映出来。重要的是,不仅输入中的键入会更新其值(归功于 onChange 处理器),任何更新 search 状态的其他操作(如清除按钮)也会更新输入。

In this sense, this input's value is controlled by React.
在这个意义上,这个输入的值由 React 控制。


But uncontrolled and controlled components don't stop with inputs.
但是,不受控制和受控制的组件并不止于输入。

Take this simple Counter — everyone's favorite React component:
这是一个简单的 Counter —— 每个人的最爱 React 组件:

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <>
      <button onClick={() => setCount(count - 1)}>-</button>
      <div>{count}</div>
      <button onClick={() => setCount(count + 1)}>+</button>
    </>
  );
}

Is it uncontrolled or controlled?
是未受控制还是受控制?

Well, React is definitely controlling the count state. So it seems like that would make Counter a controlled component.
嗯,React 确实控制着 count 的状态。所以看起来这会让 Counter 成为一个受控组件。

But what about from the perspective of an App that renders it?
但是从渲染它的 App 的角度来看呢?

function App() {
  return <Counter />;
}

Looks a lot like the uncontrolled input above, doesn't it?
看起来很像上面的未受控 input ,不是吗?


Let's make a change to Counter.
让我们对 Counter 进行更改。

Instead of giving it internal state, let's have it render from props:
而不是提供内部状态,让我们从属性中渲染它:

function App() {
  const [count, setCount] = useState(0);

  return (
    <>
      <Counter value={count} onChange={setCount} />

      <p>App state: {count}</p>

      <button onClick={() => setCount(0)}>Reset</button>
    </>
  );
}

Counter is now being controlled by App. Instead of managing its own internal state, it has delegated that state to its parent.
Counter 现在由 App 控制。它不再管理自己的内部状态,而是将其委托给了父级。

So, from the perspective of App, the first Counter was an uncontrolled component, and the second was a controlled component.
因此,从 App 的角度来看,第一个 Counter 是一个不受控制的组件,第二个是一个受控组件。

It seems like we've found a simple heuristic: if a component has internal state, it's uncontrolled, and if it doesn't, it's controlled.
这似乎是一个简单的启发式方法:如果一个组件有内部状态,它就是未受控的,如果没有,它就是受控的。

...almost. ...几乎。

Let's add one more wrinkle.
让我们再加一个复杂因素。


What would it look like if we brought back our counter's internal state, but added the ability for it to optionally render from props?
如果我们恢复计数器的内部状态,但增加它从属性中可选渲染的能力,它会是什么样子呢?

Something like this: 类似这样的东西:

function Counter({ value, onChange }) {
  const [internalCount, setInternalCount] = useState(0);
  const count = value ?? internalCount;

  function increment() {
    if (onChange) {
      onChange(value + 1);
    } else {
      setInternalCount(internalCount + 1);
    }
  }

  function decrement() {
    if (onChange) {
      onChange(value - 1);
    } else {
      setInternalCount(internalCount - 1);
    }
  }

  return (
    <>
      <button onClick={decrement}>-</button>
      <div>{count}</div>
      <button onClick={increment}>+</button>
    </>
  );
}

Here's the key line:
这是关键行:

const count = value ?? internalCount;

If the value prop is not passed in, our counter falls back to its internal state as its source of truth for rendering.
如果未传递 value 属性,我们的计数器将回退到其内部状态作为渲染的真实来源。

Let's update App to render two instances of Counter: one that doesn't pass in props, and one that does:
让我们更新 App 以渲染两个实例的 Counter :一个不传递属性,另一个传递属性:

function App() {
  const [count, setCount] = useState(0);

  return (
    <>
      <div>
        <Counter />
      </div>
      
      <div>
        <Counter value={count} onChange={setCount} />
        <span>App state: {count}</span>
      </div>
    </>
  );
}

From the perspective of App, the left counter is uncontrolled, because it manages its own internal state. But the right counter is controlled. App gets to determine what its value is, and how that value gets updated.
App 的角度来看,左侧计数器不受控制,因为它管理自己的内部状态。但右侧计数器是受控的。 App 可以决定其值是多少,以及如何更新该值。


So — we've written a Counter that can be either uncontrolled or controlled. The mere fact that it calls useState() is not enough for us to know which one it is. Instead, we have to look at how its used.
所以——我们编写了一个可以是未受控制或受控制的 Counter 。仅仅因为它调用 useState() ,对我们来说不足以知道它是哪一个。相反,我们必须看看它是如何被使用的。

When a component renders from internal state, its behaving as an uncontrolled component. And when it renders from props, it's being controlled by its parent. Which brings us back to our original question: is Counter uncontrolled or controlled?
当组件从内部状态渲染时,它的行为像一个未受控组件。而当它从 props 渲染时,它正被其父组件控制。这让我们回到了最初的问题: Counter 是未受控还是受控?

It all depends on who's asking the question.
它完全取决于问问题的人是谁。

Last updated: 最后更新:

Get our latest in your inbox.
获取我们最新的内容至您的邮箱。

Join our newsletter to hear about Sam and Ryan's newest blog posts, code recipes, and videos.
加入我们的通讯,了解 Sam 和 Ryan 的最新博客文章、代码食谱和视频。