状态在组件之间是隔离的。React 根据它们在 UI 树中的位置来跟踪每个状态属于哪个组件。你可以控制在重新渲染之间何时保留状态以及何时重置状态。
🌐 State is isolated between components. React keeps track of which state belongs to which component based on their place in the UI tree. You can control when to preserve state and when to reset it between re-renders.
你将学习到
- React 何时选择保留或重置状态时
- 如何强制 React 重置组件的状态
- 键和类型如何影响状态是否被保留
状态与渲染树中的位置相关联
🌐 State is tied to a position in the render tree
React 为你的 UI 中的组件结构构建 渲染树。
🌐 React builds render trees for the component structure in your UI.
当你为一个组件提供状态时,你可能会认为状态“存在”于组件内部。但实际上状态是存储在 React 中的。React 通过组件在渲染树中的位置,将它所持有的每一部分状态与正确的组件关联起来。
🌐 When you give a component state, you might think the state “lives” inside the component. But the state is actually held inside React. React associates each piece of state it’s holding with the correct component by where that component sits in the render tree.
这里,只有一个 <Counter /> JSX 标签,但它在两个不同的位置渲染:
🌐 Here, there is only one <Counter /> JSX tag, but it’s rendered at two different positions:
import { useState } from 'react'; export default function App() { const counter = <Counter />; return ( <div> {counter} {counter} </div> ); } function Counter() { const [score, setScore] = useState(0); const [hover, setHover] = useState(false); let className = 'counter'; if (hover) { className += ' hover'; } return ( <div className={className} onPointerEnter={() => setHover(true)} onPointerLeave={() => setHover(false)} > <h1>{score}</h1> <button onClick={() => setScore(score + 1)}> Add one </button> </div> ); }
这是树的样子:
🌐 Here’s how these look as a tree:


React 树
🌐 React tree
这是两个独立的计数器,因为每个计数器都在树中的自身位置进行渲染。 通常你不需要考虑使用 React 时这些位置,但理解其工作原理可能会很有用。
在 React 中,屏幕上的每个组件都有完全隔离的状态。例如,如果你并排渲染两个 Counter 组件,它们每个都会拥有自己独立的 score 和 hover 状态。
🌐 In React, each component on the screen has fully isolated state. For example, if you render two Counter components side by side, each of them will get its own, independent, score and hover states.
尝试同时单击两个计数器并注意它们不会相互影响:
🌐 Try clicking both counters and notice they don’t affect each other:
import { useState } from 'react'; export default function App() { return ( <div> <Counter /> <Counter /> </div> ); } function Counter() { const [score, setScore] = useState(0); const [hover, setHover] = useState(false); let className = 'counter'; if (hover) { className += ' hover'; } return ( <div className={className} onPointerEnter={() => setHover(true)} onPointerLeave={() => setHover(false)} > <h1>{score}</h1> <button onClick={() => setScore(score + 1)}> Add one </button> </div> ); }
如你所见,更新一个计数器时,只会更新该组件的状态:
🌐 As you can see, when one counter is updated, only the state for that component is updated:


更新状态
🌐 Updating state
只要你在树中的相同位置渲染相同的组件,React 就会保留该状态。要验证这一点,可以同时增加两个计数器,然后通过取消勾选“渲染第二个计数器”复选框来移除第二个组件,再通过重新勾选它将其添加回来:
🌐 React will keep the state around for as long as you render the same component at the same position in the tree. To see this, increment both counters, then remove the second component by unchecking “Render the second counter” checkbox, and then add it back by ticking it again:
import { useState } from 'react'; export default function App() { const [showB, setShowB] = useState(true); return ( <div> <Counter /> {showB && <Counter />} <label> <input type="checkbox" checked={showB} onChange={e => { setShowB(e.target.checked) }} /> Render the second counter </label> </div> ); } function Counter() { const [score, setScore] = useState(0); const [hover, setHover] = useState(false); let className = 'counter'; if (hover) { className += ' hover'; } return ( <div className={className} onPointerEnter={() => setHover(true)} onPointerLeave={() => setHover(false)} > <h1>{score}</h1> <button onClick={() => setScore(score + 1)}> Add one </button> </div> ); }
注意,当你停止渲染第二个计数器的那一刻,它的状态就会完全消失。这是因为当 React 移除一个组件时,它会销毁该组件的状态。
🌐 Notice how the moment you stop rendering the second counter, its state disappears completely. That’s because when React removes a component, it destroys its state.


删除组件
🌐 Deleting a component
当你勾选“渲染第二个计数器”时,第二个 Counter 及其状态将从零开始初始化 (score = 0) 并添加到 DOM 中。
🌐 When you tick “Render the second counter”, a second Counter and its state are initialized from scratch (score = 0) and added to the DOM.


添加组件
🌐 Adding a component
React 会在组件在 UI 树中的位置被渲染时,保留它的状态。 如果组件被移除,或者在同一位置渲染了不同的组件,React 就会丢弃它的状态。
相同位置的相同组件保留状态
🌐 Same component at the same position preserves state
在这个例子中,有两个不同的 <Counter /> 标签:
🌐 In this example, there are two different <Counter /> tags:
import { useState } from 'react'; export default function App() { const [isFancy, setIsFancy] = useState(false); return ( <div> {isFancy ? ( <Counter isFancy={true} /> ) : ( <Counter isFancy={false} /> )} <label> <input type="checkbox" checked={isFancy} onChange={e => { setIsFancy(e.target.checked) }} /> Use fancy styling </label> </div> ); } function Counter({ isFancy }) { const [score, setScore] = useState(0); const [hover, setHover] = useState(false); let className = 'counter'; if (hover) { className += ' hover'; } if (isFancy) { className += ' fancy'; } return ( <div className={className} onPointerEnter={() => setHover(true)} onPointerLeave={() => setHover(false)} > <h1>{score}</h1> <button onClick={() => setScore(score + 1)}> Add one </button> </div> ); }
当你勾选或取消勾选复选框时,计数器状态不会被重置。无论 isFancy 是 true 还是 false,你总是将 <Counter /> 作为从根 App 组件返回的 div 的第一个子元素:
🌐 When you tick or clear the checkbox, the counter state does not get reset. Whether isFancy is true or false, you always have a <Counter /> as the first child of the div returned from the root App component:


更新 App 状态不会重置 Counter,因为 Counter 保持在相同的位置
🌐 Updating the App state does not reset the Counter because Counter stays in the same position
它是相同位置的相同组件,因此从 React 的角度来看,它是相同的计数器。
🌐 It’s the same component at the same position, so from React’s perspective, it’s the same counter.
同一位置不同组件重置状态
🌐 Different components at the same position reset state
在此示例中,勾选复选框将会把 <Counter> 替换为 <p>:
🌐 In this example, ticking the checkbox will replace <Counter> with a <p>:
import { useState } from 'react'; export default function App() { const [isPaused, setIsPaused] = useState(false); return ( <div> {isPaused ? ( <p>See you later!</p> ) : ( <Counter /> )} <label> <input type="checkbox" checked={isPaused} onChange={e => { setIsPaused(e.target.checked) }} /> Take a break </label> </div> ); } function Counter() { const [score, setScore] = useState(0); const [hover, setHover] = useState(false); let className = 'counter'; if (hover) { className += ' hover'; } return ( <div className={className} onPointerEnter={() => setHover(true)} onPointerLeave={() => setHover(false)} > <h1>{score}</h1> <button onClick={() => setScore(score + 1)}> Add one </button> </div> ); }
在这里,你在相同的位置切换不同的组件类型。最初,<div> 的第一个子元素包含一个 Counter。但是当你换入一个 p 时,React 从 UI 树中移除了 Counter 并销毁了它的状态。
🌐 Here, you switch between different component types at the same position. Initially, the first child of the <div> contained a Counter. But when you swapped in a p, React removed the Counter from the UI tree and destroyed its state.


当 Counter 变为 p 时,Counter 被删除,p 被添加
🌐 When Counter changes to p, the Counter is deleted and the p is added


在切换回来时,p 被删除,Counter 被添加
🌐 When switching back, the p is deleted and the Counter is added
另外,**当你在同一位置渲染不同的组件时,它会重置其整个子树的状态。**要了解这是如何工作的,请先增加计数器,然后勾选复选框:
🌐 Also, when you render a different component in the same position, it resets the state of its entire subtree. To see how this works, increment the counter and then tick the checkbox:
import { useState } from 'react'; export default function App() { const [isFancy, setIsFancy] = useState(false); return ( <div> {isFancy ? ( <div> <Counter isFancy={true} /> </div> ) : ( <section> <Counter isFancy={false} /> </section> )} <label> <input type="checkbox" checked={isFancy} onChange={e => { setIsFancy(e.target.checked) }} /> Use fancy styling </label> </div> ); } function Counter({ isFancy }) { const [score, setScore] = useState(0); const [hover, setHover] = useState(false); let className = 'counter'; if (hover) { className += ' hover'; } if (isFancy) { className += ' fancy'; } return ( <div className={className} onPointerEnter={() => setHover(true)} onPointerLeave={() => setHover(false)} > <h1>{score}</h1> <button onClick={() => setScore(score + 1)}> Add one </button> </div> ); }
当你点击复选框时,计数器状态会被重置。虽然你渲染了一个 Counter,div 的第一个子元素从 section 变为 div。当子元素 section 从 DOM 中移除时,它下面的整个树(包括 Counter 及其状态)也被销毁了。
🌐 The counter state gets reset when you click the checkbox. Although you render a Counter, the first child of the div changes from a section to a div. When the child section was removed from the DOM, the whole tree below it (including the Counter and its state) was destroyed as well.


当 section 变为 div 时,section 会被删除,新的 div 会被添加
🌐 When section changes to div, the section is deleted and the new div is added


切换回来时,div 会被删除,新的 section 会被添加
🌐 When switching back, the div is deleted and the new section is added
通常情况下,如果你想在重新渲染之间保留状态,你的组件树结构需要在每次渲染之间“保持一致”。如果结构不同,状态会被销毁,因为当 React 从树中移除一个组件时会销毁它的状态。
🌐 As a rule of thumb, if you want to preserve the state between re-renders, the structure of your tree needs to “match up” from one render to another. If the structure is different, the state gets destroyed because React destroys state when it removes a component from the tree.
在同一位置重置状态
🌐 Resetting state at the same position
默认情况下,React 会在组件保持在相同位置时保留其状态。通常情况下,这正是你想要的,因此将其作为默认行为是有道理的。但有时,你可能希望重置组件的状态。考虑这个应用,它允许两名玩家在每轮中记录他们的分数:
🌐 By default, React preserves state of a component while it stays at the same position. Usually, this is exactly what you want, so it makes sense as the default behavior. But sometimes, you may want to reset a component’s state. Consider this app that lets two players keep track of their scores during each turn:
import { useState } from 'react'; export default function Scoreboard() { const [isPlayerA, setIsPlayerA] = useState(true); return ( <div> {isPlayerA ? ( <Counter person="Taylor" /> ) : ( <Counter person="Sarah" /> )} <button onClick={() => { setIsPlayerA(!isPlayerA); }}> Next player! </button> </div> ); } function Counter({ person }) { const [score, setScore] = useState(0); const [hover, setHover] = useState(false); let className = 'counter'; if (hover) { className += ' hover'; } return ( <div className={className} onPointerEnter={() => setHover(true)} onPointerLeave={() => setHover(false)} > <h1>{person}'s score: {score}</h1> <button onClick={() => setScore(score + 1)}> Add one </button> </div> ); }
目前,当你更换玩家时,分数会被保留。两个 Counter 出现在相同的位置,因此 React 将它们视为 相同的 Counter,只是其 person 属性发生了变化。
🌐 Currently, when you change the player, the score is preserved. The two Counters appear in the same position, so React sees them as the same Counter whose person prop has changed.
但从概念上来说,在这个应用中它们应该是两个独立的计数器。它们可能在用户界面中出现在同一个位置,但一个是泰勒的计数器,另一个是萨拉的计数器。
🌐 But conceptually, in this app they should be two separate counters. They might appear in the same place in the UI, but one is a counter for Taylor, and another is a counter for Sarah.
在它们之间切换时有两种重置状态的方法:
🌐 There are two ways to reset state when switching between them:
- 在不同位置渲染组件
- 给每个组件一个明确的身份标识
key
选项 1:在不同位置渲染组件
🌐 Option 1: Rendering a component in different positions
如果你希望这两个 Counter 独立,你可以将它们渲染在两个不同的位置:
🌐 If you want these two Counters to be independent, you can render them in two different positions:
import { useState } from 'react'; export default function Scoreboard() { const [isPlayerA, setIsPlayerA] = useState(true); return ( <div> {isPlayerA && <Counter person="Taylor" /> } {!isPlayerA && <Counter person="Sarah" /> } <button onClick={() => { setIsPlayerA(!isPlayerA); }}> Next player! </button> </div> ); } function Counter({ person }) { const [score, setScore] = useState(0); const [hover, setHover] = useState(false); let className = 'counter'; if (hover) { className += ' hover'; } return ( <div className={className} onPointerEnter={() => setHover(true)} onPointerLeave={() => setHover(false)} > <h1>{person}'s score: {score}</h1> <button onClick={() => setScore(score + 1)}> Add one </button> </div> ); }
- 最初,
isPlayerA是true。所以第一个位置包含Counter状态,第二个位置是空的。 - 当你点击“下一位玩家”按钮时,第一个位置会被清空,但第二个位置现在包含一个
Counter。


初始状态
🌐 Initial state


点击“下一步”
🌐 Clicking “next”


再次点击“下一步”
🌐 Clicking “next” again
每个 Counter 的状态在每次从 DOM 中移除时都会被销毁。这就是为什么每次点击按钮它们都会重置的原因。
🌐 Each Counter’s state gets destroyed each time it’s removed from the DOM. This is why they reset every time you click the button.
当你只有少数几个独立组件在同一位置渲染时,这个解决方案很方便。在这个例子中,你只有两个组件,因此在 JSX 中分别渲染它们并不麻烦。
🌐 This solution is convenient when you only have a few independent components rendered in the same place. In this example, you only have two, so it’s not a hassle to render both separately in the JSX.
选项 2:使用键重置状态
🌐 Option 2: Resetting state with a key
还有另一种更通用的方法来重置组件的状态。
🌐 There is also another, more generic, way to reset a component’s state.
你可能在渲染列表时见过 key。键不仅仅用于列表!你可以使用键让 React 区分任何组件。默认情况下,React 使用父组件内的顺序(“第一个计数器”、“第二个计数器”)来区分组件。但键可以让你告诉 React,这不仅仅是一个第一个计数器,或一个第二个计数器,而是一个特定的计数器——例如,Taylor 的计数器。这样,无论 Taylor 的计数器出现在树的何处,React 都能识别它!
🌐 You might have seen keys when rendering lists. Keys aren’t just for lists! You can use keys to make React distinguish between any components. By default, React uses order within the parent (“first counter”, “second counter”) to discern between components. But keys let you tell React that this is not just a first counter, or a second counter, but a specific counter—for example, Taylor’s counter. This way, React will know Taylor’s counter wherever it appears in the tree!
在这个例子中,这两个 <Counter /> 即使出现在 JSX 的同一个位置,也不会共享状态:
🌐 In this example, the two <Counter />s don’t share state even though they appear in the same place in JSX:
import { useState } from 'react'; export default function Scoreboard() { const [isPlayerA, setIsPlayerA] = useState(true); return ( <div> {isPlayerA ? ( <Counter key="Taylor" person="Taylor" /> ) : ( <Counter key="Sarah" person="Sarah" /> )} <button onClick={() => { setIsPlayerA(!isPlayerA); }}> Next player! </button> </div> ); } function Counter({ person }) { const [score, setScore] = useState(0); const [hover, setHover] = useState(false); let className = 'counter'; if (hover) { className += ' hover'; } return ( <div className={className} onPointerEnter={() => setHover(true)} onPointerLeave={() => setHover(false)} > <h1>{person}'s score: {score}</h1> <button onClick={() => setScore(score + 1)}> Add one </button> </div> ); }
在 Taylor 和 Sarah 之间切换不会保留状态。这是因为你给了他们不同的 key:
🌐 Switching between Taylor and Sarah does not preserve the state. This is because you gave them different keys:
{isPlayerA ? (
<Counter key="Taylor" person="Taylor" />
) : (
<Counter key="Sarah" person="Sarah" />
)}指定 key 告诉 React 使用 key 本身作为位置的一部分,而不是它们在父元素中的顺序。这就是为什么即使你在 JSX 中在相同的位置渲染它们,React 也会将它们视为两个不同的计数器,因此它们永远不会共享状态。每次计数器出现在屏幕上时,它的状态都会被创建。每次它被移除时,它的状态都会被销毁。在它们之间切换会不断重置它们的状态。
🌐 Specifying a key tells React to use the key itself as part of the position, instead of their order within the parent. This is why, even though you render them in the same place in JSX, React sees them as two different counters, and so they will never share state. Every time a counter appears on the screen, its state is created. Every time it is removed, its state is destroyed. Toggling between them resets their state over and over.
使用键重置表单
🌐 Resetting a form with a key
使用键重置状态在处理表单时特别有用。
🌐 Resetting state with a key is particularly useful when dealing with forms.
在这个聊天应用中,<Chat> 组件包含文本输入状态:
🌐 In this chat app, the <Chat> component contains the text input state:
import { useState } from 'react'; import Chat from './Chat.js'; import ContactList from './ContactList.js'; export default function Messenger() { const [to, setTo] = useState(contacts[0]); return ( <div> <ContactList contacts={contacts} selectedContact={to} onSelect={contact => setTo(contact)} /> <Chat contact={to} /> </div> ) } const contacts = [ { id: 0, name: 'Taylor', email: 'taylor@mail.com' }, { id: 1, name: 'Alice', email: 'alice@mail.com' }, { id: 2, name: 'Bob', email: 'bob@mail.com' } ];
尝试在输入框中输入一些内容,然后按“ Alice”或“ Bob”来选择不同的接收者。你会注意到输入状态得以保留,因为 <Chat> 被渲染在树中的相同位置。
🌐 Try entering something into the input, and then press “Alice” or “Bob” to choose a different recipient. You will notice that the input state is preserved because the <Chat> is rendered at the same position in the tree.
在许多应用中,这可能是期望的行为,但在聊天应用中不是! 你不希望因为意外点击而让用户将已经输入的消息发送给错误的人。为了解决这个问题,添加一个 key:
<Chat key={to.id} contact={to} />这确保了当你选择不同的接收者时,Chat 组件将从头重新创建,包括其下方的任何状态。React 还会重新创建 DOM 元素,而不是重复使用它们。
🌐 This ensures that when you select a different recipient, the Chat component will be recreated from scratch, including any state in the tree below it. React will also re-create the DOM elements instead of reusing them.
现在切换收件人总是会清除文本字段:
🌐 Now switching the recipient always clears the text field:
import { useState } from 'react'; import Chat from './Chat.js'; import ContactList from './ContactList.js'; export default function Messenger() { const [to, setTo] = useState(contacts[0]); return ( <div> <ContactList contacts={contacts} selectedContact={to} onSelect={contact => setTo(contact)} /> <Chat key={to.id} contact={to} /> </div> ) } const contacts = [ { id: 0, name: 'Taylor', email: 'taylor@mail.com' }, { id: 1, name: 'Alice', email: 'alice@mail.com' }, { id: 2, name: 'Bob', email: 'bob@mail.com' } ];
深入研究
🌐 Preserving state for removed components
在一个真实的聊天应用中,当用户再次选择之前的收件人时,你可能希望恢复输入状态。有几种方法可以让一个不再可见的组件保持“活跃”状态:
🌐 In a real chat app, you’d probably want to recover the input state when the user selects the previous recipient again. There are a few ways to keep the state “alive” for a component that’s no longer visible:
- 你可以渲染 所有 聊天,而不仅仅是当前的那个,但通过 CSS 隐藏其他所有聊天。聊天不会从树中被移除,因此它们的本地状态会被保留。这个解决方案对于简单的 UI 非常有效。但是,如果被隐藏的树很大并且包含大量 DOM 节点,速度可能会非常慢。
- 你可以提升状态并在父组件中为每个接收者保存待处理消息。这样,当子组件被移除时也无所谓,因为是父组件保存了重要信息。这是最常见的解决方案。
- 你也可以除了 React 状态之外使用不同的来源。例如,你可能希望即使用户意外关闭页面,消息草稿也能保留。要实现这一点,你可以让
Chat组件通过读取localStorage来初始化其状态,并且也将草稿保存在那里。
无论你选择哪种策略,与 Alice 的聊天在概念上都不同于与 Bob 的聊天,因此根据当前的接收者为 <Chat> 树提供 key 是有意义的。
🌐 No matter which strategy you pick, a chat with Alice is conceptually distinct from a chat with Bob, so it makes sense to give a key to the <Chat> tree based on the current recipient.
回顾
- 只要同一组件在同一位置渲染,React 就会保持状态。
- 状态不会保存在 JSX 标签中。它与放置该 JSX 的树位置相关联。
- 你可以通过给它一个不同的键来强制子树重置其状态。
- 不要嵌套组件定义,否则你会不小心重置状态。
挑战 1 of 5: 修复消失的输入文本
🌐 Fix disappearing input text
当你按下按钮时,此示例会显示一条消息。然而,按下按钮也会意外地重置输入。为什么会发生这种情况?修复它,使按下按钮不会重置输入文本。
🌐 This example shows a message when you press the button. However, pressing the button also accidentally resets the input. Why does this happen? Fix it so that pressing the button does not reset the input text.
import { useState } from 'react'; export default function App() { const [showHint, setShowHint] = useState(false); if (showHint) { return ( <div> <p><i>Hint: Your favorite city?</i></p> <Form /> <button onClick={() => { setShowHint(false); }}>Hide hint</button> </div> ); } return ( <div> <Form /> <button onClick={() => { setShowHint(true); }}>Show hint</button> </div> ); } function Form() { const [text, setText] = useState(''); return ( <textarea value={text} onChange={e => setText(e.target.value)} /> ); }