v19.2Latest

在组件间共享状态

有时,你会希望两个组件的状态始终保持同步。要实现这一点,需要从这两个组件中移除状态,将其提升到它们最近的共同父组件中,然后通过 props 向下传递。这被称为状态提升,也是编写 React 代码时最常做的事情之一。

您将学习
  • 如何通过状态提升在组件间共享状态
  • 什么是受控组件和非受控组件

通过示例学习状态提升

在这个例子中,父组件Accordion渲染了两个独立的Panel组件:

  • Accordion
    • Panel
    • Panel

每个Panel组件都有一个布尔类型的isActive状态,用于决定其内容是否可见。

点击两个面板的“显示”按钮:

请注意,点击一个面板的按钮不会影响另一个面板——它们是相互独立的。

展示三个组件树的图表,一个父组件标记为Accordion,两个子组件标记为Panel。两个Panel组件都包含值为false的isActive状态。展示三个组件树的图表,一个父组件标记为Accordion,两个子组件标记为Panel。两个Panel组件都包含值为false的isActive状态。

最初,每个PanelisActive状态都是false,因此它们都显示为折叠状态。

与上一张图表相同,第一个子Panel组件的isActive状态被高亮显示,表示点击后其值被设为true。第二个Panel组件仍包含false值。与上一张图表相同,第一个子Panel组件的isActive状态被高亮显示,表示点击后其值被设为true。第二个Panel组件仍包含false值。

点击任意一个Panel的按钮只会更新该Panel自身的isActive状态。

但现在假设你想修改设计,使得在任何时候都只展开一个面板。按照这个设计,展开第二个面板时应该折叠第一个面板。你会如何实现?

要协调这两个面板,你需要通过三个步骤将它们的状态“提升”到父组件:

  1. 移除子组件中的状态。
  2. 从共同父组件传递硬编码的数据。
  3. 向共同父组件添加状态,并将其与事件处理函数一起向下传递。

这将允许Accordion组件协调两个Panel组件,并确保每次只展开一个。

步骤 1:从子组件中移除状态

你将把PanelisActive状态的控制权交给其父组件。这意味着父组件会将isActive作为 prop 传递给Panel。首先,Panel组件中移除这一行:

相反,将 isActive添加到Panel的属性列表中:

现在,Panel的父组件可以通过向下传递属性isActive控制它。 相反,Panel 组件现在对 没有控制权 的值 isActive——这完全取决于父组件!

步骤 2:从公共父组件传递硬编码数据

要提升状态,你必须找到你想要协调的 两个 子组件的最接近的公共父组件:

  • Accordion(最接近的公共父组件)
    • Panel
    • Panel

在这个例子中,它是Accordion组件。由于它位于两个面板之上,并且可以控制它们的属性,因此它将成为哪个面板当前处于活动状态的“单一事实来源”。让Accordion 组件将一个硬编码的 isActive值(例如,true)传递给两个面板:

尝试编辑 Accordion组件中的硬编码isActive 值,并查看屏幕上的结果。

步骤 3:向公共父组件添加状态

提升状态通常会改变你存储为状态的内容的性质。

在这种情况下,一次应该只有一个面板处于活动状态。这意味着公共父组件Accordion 需要跟踪 哪个面板是活动面板。它可以使用一个数字作为活动布尔 的索引,而不是 Panel值作为状态变量:

activeIndex0时,第一个面板处于活动状态;当它为1时,第二个面板处于活动状态。

点击任一 Panel中的“显示”按钮都需要更改Accordion中的活动索引。一个Panel不能直接设置activeIndex 状态,因为它是在 Accordion内部定义的。Accordion 组件需要 明确允许 Panel 组件通过 向下传递事件处理函数作为属性来更改其状态:

现在,<button> 内部的 Panel 将使用 onShow属性作为其点击事件处理函数:

至此,状态提升就完成了!将状态移动到共同的父组件中,使你能够协调两个面板。使用活动索引而不是两个“是否显示”标志,确保了在给定时间只有一个面板处于活动状态。将事件处理函数传递给子组件,则允许子组件改变父组件的状态。

图表显示了一个包含三个组件的树:一个标记为 Accordion 的父组件和两个标记为 Panel 的子组件。Accordion 包含一个值为 0 的 activeIndex,该值转换为 isActive 值 true 传递给第一个 Panel,以及 isActive 值 false 传递给第二个 Panel。图表显示了一个包含三个组件的树:一个标记为 Accordion 的父组件和两个标记为 Panel 的子组件。Accordion 包含一个值为 0 的 activeIndex,该值转换为 isActive 值 true 传递给第一个 Panel,以及 isActive 值 false 传递给第二个 Panel。

最初,AccordionactiveIndex0,因此第一个Panel接收到isActive = true

与上一张相同的图表,父组件 Accordion 的 activeIndex 值被高亮显示,表示一次点击后值变为 1。流向两个子组件 Panel 的数据流也被高亮,传递给每个子组件的 isActive 值被设置为相反的值:第一个 Panel 为 false,第二个 Panel 为 true。与上一张相同的图表,父组件 Accordion 的 activeIndex 值被高亮显示,表示一次点击后值变为 1。流向两个子组件 Panel 的数据流也被高亮,传递给每个子组件的 isActive 值被设置为相反的值:第一个 Panel 为 false,第二个 Panel 为 true。

AccordionactiveIndex 状态变为 1时,第二个Panel将接收到isActive = true

Deep Dive
受控组件与非受控组件

每个状态的单一数据源

在 React 应用中,许多组件将拥有自己的状态。有些状态可能“驻留”在靠近叶子组件(树底部的组件)的位置,例如输入框。其他状态可能“驻留”在应用顶部附近。例如,即使是客户端路由库,通常也是通过将当前路由存储在 React 状态中,并通过 props 向下传递来实现的!

对于每个独立的状态片段,你将选择“拥有”它的组件。 这一原则也被称为拥有 “单一数据源”。这并不意味着所有状态都存放在一个地方——而是对于每个状态片段,都有一个特定的 组件来保存该信息。与其在组件之间复制共享状态,不如将其 提升到它们共同的父组件中,然后向下传递给需要它的子组件。

你的应用会随着你的工作而改变。在弄清楚每个状态片段“驻留”在何处时,将状态向下移动或向上移动是很常见的。这都是过程的一部分!

要了解在包含更多组件的实践中这种感觉如何,请阅读React 哲学。

回顾

  • 当你想协调两个组件时,将它们的状态提升到共同的父组件中。
  • 然后通过共同的父组件向下传递信息。
  • 向下传递事件处理函数,以便子组件可以改变父组件的状态。
  • 将组件视为“受控”(由 props 驱动)或“非受控”(由 state 驱动)是很有用的。

尝试一些挑战

Challenge 1 of 2:同步输入框 #

这两个输入框是独立的。让它们保持同步:编辑一个输入框应该用相同的文本更新另一个输入框,反之亦然。