在组件之间共享状态
有时,您希望两个组件的状态始终一起更改。 要做到这一点,从他们两个中删除状态,将其移动到他们最近的共同父母,然后通过props将其传递给他们。 这被称为提升状态,这是您在编写 React 代码时最常做的事情之一。
你将学习
- 如何通过提升状态在组件之间共享状态
- 什么是受控和非受控组件
通过示例提升状态
在此示例中,父 Accordion 组件呈现两个单独的Panel:
- Accordion
- Panel
- Panel
每个 Panel 组件都有一个布尔值 isActive 状态,用于确定其内容是否可见。
按下两个面板的 Show 按钮:
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="About">
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="Etymology">
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>
</>
);
}
注意按下一个面板的按钮不会影响另一个面板——它们是独立的。
最初,每个 Panel 的 isActive 状态都是 false,因此它们都显示为折叠状态单击任一 Panel 的按钮将仅更新该 Panel 的 isActive 状态
但现在假设您想要更改它,以便在任何给定时间只展开一个面板。 使用该设计,展开第二个面板应该会折叠第一个面板。 你会怎么做?
要协调这两个面板,您需要通过三个步骤将它们的状态“提升”到父组件:
- 从子组件中删除状态。
- 从公共父级传递硬编码数据。
- 将状态添加到公共父级并将其与事件处理程序一起向下传递。
这将允许 Accordion 组件协调两个面板,并且一次只展开一个。
第 1 步:从子组件中删除状态
您将控制面板的 isActive 给它的父组件。 这意味着父组件会将 isActive 作为 prop 传递给 Panel。 首先从 Panel 组件中删除这一行:
const [isActive, setIsActive] = useState(false);
作为代替,将 isActive 添加到面板的props中:
function Panel({ title, children, isActive }) {
现在 Panel 的父组件可以通过将其作为 prop 向下传递来控制 isActive。 相反,Panel 组件现在无法控制 isActive 的值——它现在取决于父组件!
第 2 步:传递来自公共父级的硬编码数据
要提升状态,您必须找到要协调的两个子组件的最接近公共父组件:
- Accordion(最近的共同父母)
- Panel
- Panel
在这个例子中,它是 Accordion 组件。 由于它位于两个Panel上方并且可以控制它们的props,因此它将成为当前处于活动状态的面板的“真实来源”。 使 Accordion 组件将硬编码值 isActive(例如,true)传递给两个面板:
import { useState } from 'react';
export default function Accordion() {
return (
<>
<h2>Almaty, Kazakhstan</h2>
<Panel title="About" 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="Etymology" 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 值,然后在屏幕上查看结果。
第 3 步:将状态添加到公共父级
提升状态通常会改变你存储的状态的性质。
在这种情况下,一次只能激活一个Panel。 这意味着 Accordion 公共父组件需要跟踪哪个Panel是活动Panel。 它可以使用数字作为状态变量的活动面板的索引,而不是布尔值:
const [activeIndex, setActiveIndex] = useState(0);
当 activeIndex 为 0 时,第一个面板处于活动状态,为 1 时,第二个面板处于活动状态。
单击任一面板中的“ Show”按钮需要更改 Accordion 中的活动索引。 Panel 不能直接设置 activeIndex 状态,因为它是在 Accordion 内部定义的。 Accordion 组件需要明确允许 Panel 组件通过将事件处理程序作为 prop 向下传递来更改其状态:
<>
<Panel
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
...
</Panel>
<Panel
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
...
</Panel>
</>
面板内的 <button> 现在将使用 onShow 属性作为其点击事件处理程序:
import { useState } from 'react';
export default function Accordion() {
const [activeIndex, setActiveIndex] = useState(0);
return (
<>
<h2>Almaty, Kazakhstan</h2>
<Panel
title="About"
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="Etymology"
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>
);
}
这样就完成了状态的提升! 将状态移动到公共父组件中允许您协调两个面板。 使用活动索引而不是两个“显示”标志可确保在给定时间只有一个面板处于活动状态。 并将事件处理程序传递给孩子允许孩子更改父母的状态。
一开始,Accordion的activeIndex为0,所以第一个Panel收到isActive = true
当 Accordion 的 activeIndex 状态更改为 1 时,第二个 Panel 改为接收 isActive = true
深度阅读:受控和非受控组件
通常将具有某些本地状态的组件称为“不受控制”。 例如,带有 isActive 状态变量的原始 Panel 组件是不受控制的,因为它的父级无法影响面板是否处于活动状态。
相反,当组件中的重要信息由 props 而不是其自身的本地状态驱动时,您可能会说组件是“受控的”。 这让父组件可以完全指定它的行为。 带有 isActive 属性的最终 Panel 组件由 Accordion 组件控制。
不受控制的组件在其父组件中更易于使用,因为它们需要的配置更少。 但是当你想将它们协调在一起时,它们就不那么灵活了。 受控组件具有最大的灵活性,但它们需要父组件使用 props 完全配置它们。
实际上,“受控”和“不受控”并不是严格的技术术语——每个组件通常都有本地状态和 props 的某种组合。 但是,这是讨论组件的设计方式和它们提供的功能的有用方式。
在编写组件时,考虑其中哪些信息应该受控(通过 props),哪些信息应该不受控(通过 state)。 但是您随时可以改变主意并在以后进行重构。
每个state的单一事实来源
在 React 应用程序中,许多组件都有自己的状态。 一些状态可能像输入一样“生活”在叶子组件(树底部的组件)附近。 其他状态可能“住在”更靠近应用程序顶部的位置。 例如,即使是客户端路由库,通常也是通过将当前路由存储在 React 状态中,并通过 props 向下传递来实现的!
对于每个独特的状态,您将选择“拥有”它的组件。 这一原则也被称为拥有“单一事实来源”。 这并不意味着所有状态都存在于一个地方——而是对于每个状态,都有一个特定的组件来保存该信息。 不是在组件之间复制共享状态,而是将它提升到它们共同的共享父级,然后将它传递给需要它的子级。
您的应用程序会随着您的工作而改变。 当您仍在弄清楚每个状态“存在”的位置时,您通常会向下移动状态或向后移动状态。 这是整个过程的一部分!
要了解更多组件在实践中的感觉,请阅读按React的风格思考。
回顾
- 当您想要协调两个组件时,将它们的状态移动到它们的共同父级。
- 然后通过他们共同父母的道具将信息传递下去。
- 最后,向下传递事件处理程序,以便子级可以更改父级的状态。
- 将组件视为“受控”(由 props 驱动)或“不受控”(由状态驱动)是很有用的。
网友评论