使用 Context 深层传递参数:告别 Prop 逐级透传
通常情况下,我们通过 Props 把数据从父组件传给子组件。这在层级较浅时非常清晰自然。
但是想象一下这个场景:
在你的整个应用顶层,有一个表示当前“主题颜色(深色模式/浅色模式)”的状态。而页面深处的一个微小按钮需要读取这个颜色。
为了把这个变量送到底层按钮,你必须强迫中间所有的组件(App Layout Sidebar Menu MenuItem Button)都加上一个 theme prop,并像接力棒一样一级一级机械地传下去。
这种被迫穿透大量中间无辜组件的传参方式,被称为 Prop 逐级透传(Prop Drilling)。
为了彻底消灭这种繁琐痛苦的写法,React 提供了 Context(上下文)。它就像在顶层组件与底层子节点之间架设了一座 超时空传送门!
1. 使用 Context 的标准三部曲
无论多复杂的 Context 应用,都遵循以下清晰的三部曲:
第一步:创建 Context(Create the context)
使用 createContext 创建并导出一个 Context 对象。可以给它一个默认值:
// LevelContext.js
import { createContext } from 'react';
// 初始默认层级为 1
export const LevelContext = createContext(1);
第二步:在需要数据的深层子组件中使用 Context
在需要读取数据的任何子组件内部,引入 useContext Hook,直接把对应的 Context 传给它:
// Heading.js
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
export default function Heading({ children }) {
// 魔法时刻:直接从传送门获取上层提供的数据!
const level = useContext(LevelContext);
switch (level) {
case 1:
return <h1>{children}</h1>;
case 2:
return <h2>{children}</h2>;
case 3:
return <h3>{children}</h3>;
case 4:
return <h4>{children}</h4>;
default:
throw Error('未知的层级: ' + level);
}
}
请注意看!Heading 组件完全不需要从自己的 Props 里面接收 level!
第三步:在上层父组件中提供 Context(Provide the context)
在需要向下方子树广播数据的上层组件中,使用 LevelContext.Provider 把子组件包裹起来,并通过 value prop 传入要广播的值:
// Section.js
import { LevelContext } from './LevelContext.js';
export default function Section({ level, children }) {
return (
<section className="section">
<LevelContext.Provider value={level}>
{children}
</LevelContext.Provider>
</section>
);
}
2. 精彩的实战效果:嵌套标题
现在,使用这两个组件构建复杂的排版文章:
import Section from './Section.js';
import Heading from './Heading.js';
export default function Page() {
return (
<Section level={1}>
<Heading>主标题(自动渲染为 H1)</Heading>
<Section level={2}>
<Heading>副标题(自动渲染为 H2)</Heading>
<Heading>同级副标题(自动渲染为 H2)</Heading>
<Section level={3}>
<Heading>子章节(自动渲染为 H3)</Heading>
</Section>
</Section>
</Section>
);
}
所有的 Heading 都极其干净利落,不需要传任何层级参数,它们会顺着组件树自动寻找距离自己最近的那个 Provider,并读取对应的数值!
3. Context 的经典使用场景
什么时候应该使用 Context?
- 全局主题(Theming):比如切换日间模式(Light Mode)与夜间模式(Dark Mode);
- 当前登录用户(Current Account):许多深层组件需要展示当前登录者的头像与权限;
- 路由状态(Routing):路由库利用 Context 让深层链接知道当前正处于哪个 URL 路径;
- 语言国际化(Localization):多语言切换。
警惕:切勿滥用 Context! 如果只是两三层组件之间的普通数据流动,依然优先使用清晰直接的 Props。如果只是为了避免传参,优先考虑是否可以把子组件作为
children传递(组件组合模式)。
总结
- Context 解决了复杂层级深处组件访问全局或高层数据的“Prop 逐级透传”问题;
- 三部曲:
createContext创建、useContext读取、<Context.Provider value={...}>广播; - 子组件总是从离它最近的同名 Provider 中读取数据;
- 适合主题、登录凭证、语言等跨越整个子树的共享数据。