0%

已完成 0/29 关

教学模式

你可以随时调整。

此设置调整教师的教学方式,无需重新生成课程。

第 22 关 含代码 约 11 分钟

在组件间共享状态:状态提升与受控组件

在组件间共享状态:状态提升与受控组件

在前面几关中,你见证了每个组件拥有的私有独立 State。但很多时候,你希望两个或多个同级组件能够相互配合、步调一致。

例如:

  • 一个折叠面板展开时,希望其他折叠面板自动收起;
  • 一个搜索输入框输入文字时,下方的地图标记列表同时高亮;
  • 两个数字输入框,修改摄氏度时华氏度自动联动换算。

要让同级组件共享数据并产生联动,你必须将它们的状态从子组件中拿出来,提升到它们最近的公共父组件中。这就是著名的 状态提升(Lifting State Up)。


1. 经典场景:制作手风琴折叠面板(Accordion)

想象一个包含两个子面板的页面:

function Panel({ title, children }) {
  const [isActive, setIsActive] = useState(false);

  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        children
      ) : (
        <button onClick={() => setIsActive(true)}>展开</button>
      )}
    </section>
  );
}

export default function Accordion() {
  return (
    <>
      <h2>历史学者档案</h2>
      <Panel title="关于">
        阿尔马维尔(Armavir)是亚美尼亚最古老的城市之一。
      </Panel>
      <Panel title="词源">
        这个名字源自亚拉拉特平原古老历史遗迹。
      </Panel>
    </>
  );
}

此时,每个 Panel 都有自己独立的 isActive 状态。点击第一个面板的展开,第一个打开;再点击第二个,第二个也打开。 但如果需求变更为:这是一个严格的手风琴,同一时刻只允许打开一个面板,打开其中一个必须自动合上另一个!

子组件无法互相看到彼此的私有状态。改造开始!


2. 状态提升标准三步走

步骤一:从子组件中移除 State

把子组件原本私有的 isActive 删掉,改为通过 Props 接收外部传入的控制信号:

function Panel({ title, children, isActive, onShow }) {
  return (
    <section className="panel">
      <h3>{title}</h3>
      {isActive ? (
        children
      ) : (
        <button onClick={onShow}>展开</button>
      )}
    </section>
  );
}

现在,Panel 变成了一个完全服从上级指挥的组件:让它开就开,点击后它只负责触发 onShow 向上汇报!


步骤二:在公共父组件中声明 State

在共同的父组件 Accordion 中,我们需要记住“当前哪个面板是打开的”。我们用一个数字索引 activeIndex 来记录:

export default function Accordion() {
  // 0 代表第一个面板打开,1 代表第二个打开
  const [activeIndex, setActiveIndex] = useState(0);

  // ...
}

步骤三:把状态与更新回调传递给子组件

父组件根据当前的 activeIndex,分别给两个子组件传入计算好的布尔值和回调函数:

export default function Accordion() {
  const [activeIndex, setActiveIndex] = useState(0);

  return (
    <>
      <h2>历史学者档案</h2>
      <Panel
        title="关于"
        isActive={activeIndex === 0}
        onShow={() => setActiveIndex(0)}
      >
        阿尔马维尔(Armavir)是亚美尼亚最古老的城市之一。
      </Panel>
      <Panel
        title="词源"
        isActive={activeIndex === 1}
        onShow={() => setActiveIndex(1)}
      >
        这个名字源自亚拉拉特平原古老历史遗迹。
      </Panel>
    </>
  );
}

大功告成!现在只要点击第二个面板的展开,setActiveIndex(1) 执行,父组件重新渲染:

  • 第一个面板收到的 isActive 变成了 0 === 1(即 false),自动收起;
  • 第二个面板收到的 isActive 变成了 1 === 1(即 true),顺利展开!

3. 受控组件 vs 非受控组件

在完成状态提升后,我们顺理成章地接触到了 React 中极其重要的两个术语:

  • 受控组件(Controlled Component): 组件的重要信息完全由父组件通过 Props 驱动控制。组件自身不保有最终决策权(比如改造后的 Panel,开不开由父级说了算)。受控组件极具弹性和可预测性,非常容易实现联动和统一管理;
  • 非受控组件(Uncontrolled Component): 组件内部自己持有私有的状态,父组件无法直接干涉其展现(比如最初那个自带 useState 的 Panel)。非受控组件在简单孤立场景下使用很省心,但极难与其他组件产生复杂的联动。

在实际开发中,当一个组件需要和外界产生配合时,将其重构为受控组件是标准做法。


总结

  • 当多个组件需要同步数据或联动展示时,将状态提升到它们共同的父组件中;
  • 状态提升遵循三步:子组件抽离 state 为 props、父组件维护统一 state、父组件向下传递数据与回调;
  • 状态提升体现了单一真实数据源(Single Source of Truth)的优雅理念;
  • 通过 props 全面控制子组件行为的模式,称为受控组件。
下一站:保留和重置 State:组件在 UI 树中的位置与 key 的作用 →
© 2026 React 官方教程