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.
它完全取决于问问题的人是谁。