有时,你希望两个组件的状态总是一起变化。要做到这一点,需要从它们两个中移除状态,把状态移动到它们最近的公共父组件,然后通过 props 将状态传递给它们。这被称为提升状态(lifting state up),它是你在编写 React 代码时最常做的事情之一。
🌐 Sometimes, you want the state of two components to always change together. To do it, remove state from both of them, move it to their closest common parent, and then pass it down to them via props. This is known as lifting state up, and it’s one of the most common things you will do writing React code.
你将学习到
- 如何通过提升状态在组件之间共享状态
- 什么是受控和非受控组件
通过示例提升状态
🌐 Lifting state up by example
在此示例中,父组件 Accordion 渲染两个独立的 Panel:
🌐 In this example, a parent Accordion component renders two separate Panels:
AccordionPanelPanel
每个 Panel 组件都有一个布尔型的 isActive 状态,用于决定其内容是否可见。
🌐 Each Panel component has a boolean isActive state that determines whether its content is visible.
按下两个面板的展示按钮:
🌐 Press the Show button for both panels:
import { useState } from 'react'; function Panel({ title, children }) { const [isActive, setIsActive] = useState(false); return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={() => setIsActive(true)}> Show </button> )} </section> ); } export default function Accordion() { return ( <> <h2>Almaty, Kazakhstan</h2> <Panel title="关于"> With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city. </Panel> <Panel title="词源"> The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple. </Panel> </> ); }
注意按下一个面板的按钮不会影响另一个面板 - 它们是独立的。
🌐 Notice how pressing one panel’s button does not affect the other panel—they are independent.


最初,每个 Panel 的 isActive 状态都是 false,所以它们看起来都是折叠的
🌐 Initially, each Panel’s isActive state is false, so they both appear collapsed


点击任意 Panel 的按钮只会更新该 Panel 的 isActive 状态
🌐 Clicking either Panel’s button will only update that Panel’s isActive state alone
但现在假设你想要将其更改为在任何给定时间只有一个面板被展开。 使用这种设计,展开第二个面板应该会折叠第一个面板。你会怎么做?
要协调这两个面板,你需要通过三步将它们的状态“提升”到父组件:
🌐 To coordinate these two panels, you need to “lift their state up” to a parent component in three steps:
- 从子组件中移除状态。
- 从公共父类传递硬编码的数据。
- 将状态添加到共同的父组件,并将其与事件处理程序一起传递下去。
这将允许 Accordion 组件协调两个 Panel,并且一次只展开一个。
🌐 This will allow the Accordion component to coordinate both Panels and only expand one at a time.
步骤 1:从子组件中移除状态
🌐 Step 1: Remove state from the child components
你将把 Panel 的 isActive 控制权交给它的父组件。这意味着父组件将把 isActive 作为 prop 传递给 Panel。首先从 Panel 组件中删除这一行:
🌐 You will give control of the Panel’s isActive to its parent component. This means that the parent component will pass isActive to Panel as a prop instead. Start by removing this line from the Panel component:
const [isActive, setIsActive] = useState(false);相反,将 isActive 添加到 Panel 的属性列表中:
🌐 And instead, add isActive to the Panel’s list of props:
function Panel({ title, children, isActive }) {现在,Panel 的父组件可以通过 作为 prop 传递下去 来 控制 isActive。相反,Panel 组件现在对 isActive 的值 没有控制权 —— 现在由父组件决定!
🌐 Now the Panel’s parent component can control isActive by passing it down as a prop. Conversely, the Panel component now has no control over the value of isActive—it’s now up to the parent component!
步骤 2:从共同的父组件传递硬编码数据
🌐 Step 2: Pass hardcoded data from the common parent
要提升状态,你必须找到你想要协调的两个子组件的最近的共同父组件:
🌐 To lift state up, you must locate the closest common parent component of both of the child components that you want to coordinate:
Accordion(最近的共同父级)PanelPanel
在这个例子中,它是 Accordion 组件。由于它位于两个面板之上并且可以控制它们的属性,它将成为当前哪个面板处于活动状态的“真实来源”。让 Accordion 组件向两个面板传递一个硬编码的 isActive 值(例如,true):
🌐 In this example, it’s the Accordion component. Since it’s above both panels and can control their props, it will become the “source of truth” for which panel is currently active. Make the Accordion component pass a hardcoded value of isActive (for example, true) to both panels:
import { useState } from 'react'; export default function Accordion() { return ( <> <h2>Almaty, Kazakhstan</h2> <Panel title="关于" isActive={true}> With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city. </Panel> <Panel title="词源" isActive={true}> The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple. </Panel> </> ); } function Panel({ title, children, isActive }) { return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={() => setIsActive(true)}> Show </button> )} </section> ); }
尝试编辑 Accordion 组件中硬编码的 isActive 值,并查看屏幕上的结果。
🌐 Try editing the hardcoded isActive values in the Accordion component and see the result on the screen.
步骤 3:向共同的父组件添加状态
🌐 Step 3: Add state to the common parent
提升状态通常会改变你存储的状态的性质。
🌐 Lifting state up often changes the nature of what you’re storing as state.
在这种情况下,一次只能有一个面板处于活动状态。这意味着 Accordion 公共父组件需要跟踪 哪个 面板是活动的。它可以使用数字作为状态变量中活动 Panel 的索引,而不是 boolean 值:
🌐 In this case, only one panel should be active at a time. This means that the Accordion common parent component needs to keep track of which panel is the active one. Instead of a boolean value, it could use a number as the index of the active Panel for the state variable:
const [activeIndex, setActiveIndex] = useState(0);当 activeIndex 是 0 时,第一个面板是活跃的,而当它是 1 时,则是第二个面板。
🌐 When the activeIndex is 0, the first panel is active, and when it’s 1, it’s the second one.
点击 Panel 中的“显示”按钮需要更改 Accordion 中的活动索引。Panel 不能直接设置 activeIndex 状态,因为它是在 Accordion 内部定义的。Accordion 组件需要明确允许 Panel 组件通过作为属性传递事件处理函数来更改其状态:
🌐 Clicking the “Show” button in either Panel needs to change the active index in Accordion. A Panel can’t set the activeIndex state directly because it’s defined inside the Accordion. The Accordion component needs to explicitly allow the Panel component to change its state by passing an event handler down as a prop:
<>
<Panel
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
...
</Panel>
<Panel
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
...
</Panel>
</>Panel 内的 <button> 现在将使用 onShow 属性作为其点击事件处理函数:
🌐 The <button> inside the Panel will now use the onShow prop as its click event handler:
import { useState } from 'react'; export default function Accordion() { const [activeIndex, setActiveIndex] = useState(0); return ( <> <h2>Almaty, Kazakhstan</h2> <Panel title="关于" isActive={activeIndex === 0} onShow={() => setActiveIndex(0)} > With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city. </Panel> <Panel title="词源" isActive={activeIndex === 1} onShow={() => setActiveIndex(1)} > The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple. </Panel> </> ); } function Panel({ title, children, isActive, onShow }) { return ( <section className="panel"> <h3>{title}</h3> {isActive ? ( <p>{children}</p> ) : ( <button onClick={onShow}> Show </button> )} </section> ); }
这就完成了状态提升!将状态移到共同的父组件中,使你能够协调两个面板。使用活动索引而不是两个“is shown”标志,确保了在任意时间只有一个面板处于活动状态。将事件处理函数传递给子组件,使子组件能够更改父组件的状态。
🌐 This completes lifting state up! Moving state into the common parent component allowed you to coordinate the two panels. Using the active index instead of two “is shown” flags ensured that only one panel is active at a given time. And passing down the event handler to the child allowed the child to change the parent’s state.


最初,Accordion 的 activeIndex 是 0,所以第一个 Panel 接收 isActive = true
🌐 Initially, Accordion’s activeIndex is 0, so the first Panel receives isActive = true


当 Accordion 的 activeIndex 状态变为 1 时,第二个 Panel 接收到的是 isActive = true
🌐 When Accordion’s activeIndex state changes to 1, the second Panel receives isActive = true instead
深入研究
🌐 Controlled and uncontrolled components
通常将具有某些本地状态的组件称为“非受控”。例如,原来的 Panel 组件具有 isActive 状态变量,因此它是非受控的,因为它的父组件无法影响面板是否处于激活状态。
🌐 It is common to call a component with some local state “uncontrolled”. For example, the original Panel component with an isActive state variable is uncontrolled because its parent cannot influence whether the panel is active or not.
相反,当组件中的重要信息由 props 驱动而不是它自己的本地状态时,你可以说该组件是“受控的”。这让父组件可以完全指定其行为。最终具有 isActive 属性的 Panel 组件由 Accordion 组件控制。
🌐 In contrast, you might say a component is “controlled” when the important information in it is driven by props rather than its own local state. This lets the parent component fully specify its behavior. The final Panel component with the isActive prop is controlled by the Accordion component.
非受控组件在其父组件内部更容易使用,因为它们需要的配置更少。但当你想要将它们协调在一起时,它们的灵活性较低。受控组件具有最大的灵活性,但它们需要父组件通过 props 完全配置。
🌐 Uncontrolled components are easier to use within their parents because they require less configuration. But they’re less flexible when you want to coordinate them together. Controlled components are maximally flexible, but they require the parent components to fully configure them with props.
在实际操作中,“受控”和“非受控”并不是严格的技术术语——每个组件通常都有一些本地状态和属性的混合。然而,这是讨论组件如何设计以及它们提供哪些功能的一种有用方式。
🌐 In practice, “controlled” and “uncontrolled” aren’t strict technical terms—each component usually has some mix of both local state and props. However, this is a useful way to talk about how components are designed and what capabilities they offer.
在编写组件时,考虑其中哪些信息应该被控制(通过 props),哪些信息应该是非控制的(通过 state)。但你总是可以改变主意,稍后进行重构。
🌐 When writing a component, consider which information in it should be controlled (via props), and which information should be uncontrolled (via state). But you can always change your mind and refactor later.
每个状态的单一事实来源
🌐 A single source of truth for each state
在一个 React 应用中,许多组件会有它们自己的状态。有些状态可能“存在”在靠近叶子组件(树底部的组件)的位置,比如输入框。其他状态可能“存在”在应用的顶部附近。例如,即使客户端路由库通常也是通过将当前路由存储在 React 状态中,并通过 props 向下传递来实现的!
🌐 In a React application, many components will have their own state. Some state may “live” close to the leaf components (components at the bottom of the tree) like inputs. Other state may “live” closer to the top of the app. For example, even client-side routing libraries are usually implemented by storing the current route in the React state, and passing it down by props!
对于每一块独特的状态,你将选择“拥有”它的组件。 这个原则也被称为拥有“单一数据源”。 这并不意味着所有状态都存放在同一个地方——而是对于每一块状态,存在一个具体的组件来持有这块信息。与其在组件之间重复共享状态,不如将其提升到它们的共同父组件,并传递给需要它的子组件。
当你在开发应用时,它会不断变化。在你还在弄清楚每个状态部分“存放”位置的时候,将状态下移或再上移是很常见的。这都是开发过程的一部分!
🌐 Your app will change as you work on it. It is common that you will move state down or back up while you’re still figuring out where each piece of the state “lives”. This is all part of the process!
要在实践中感受这种方式在更多组件上的效果,请阅读 Thinking in React.
🌐 To see what this feels like in practice with a few more components, read Thinking in React.
回顾
- 当你想要协调两个组件时,将它们的状态移动到它们的共同父级。
- 然后通过它们共同父级的属性将信息传递下去。
- 最后,向下传递事件处理程序,以便子级可以更改父级的状态。
- 把组件归类为“受控的”(由 props 驱动)或“非受控的”(由 state 驱动)是有用的。
挑战 1 of 2: 同步输入
🌐 Synced inputs
这两个输入是独立的。让它们保持同步:编辑一个输入时应更新另一个输入,使其显示相同的文本,反之亦然。
🌐 These two inputs are independent. Make them stay in sync: editing one input should update the other input with the same text, and vice versa.
import { useState } from 'react'; export default function SyncedInputs() { return ( <> <Input label="First input" /> <Input label="Second input" /> </> ); } function Input({ label }) { const [text, setText] = useState(''); function handleChange(e) { setText(e.target.value); } return ( <label> {label} {' '} <input value={text} onChange={handleChange} /> </label> ); }