01 · NOTES / WebGIS 开发
第 07 节 · 组件通信
2026 年 9 月 1 日
第 07 节 · 组件通信
📌 版本信息:React 19.x(2026-08-29 核对) 📚 来源:React 中文文档 · State 管理 | Context
一、这一节的目标
- 掌握"状态提升":兄弟组件通信的标准解法
- 掌握 Context:跨层级共享数据的正规通道
- 学会自定义 Hook 封装 Context(Provider + useXxx)
- 知道通信方案的选型边界(props → Context → 状态库)
二、状态提升:兄弟组件通过父级说话
场景:面板组件改了筛选条件,地图组件要跟着变——它们是兄弟,不能直接通信,把共享 state 提升到最近的公共父级:
function App() {
const [filter, setFilter] = useState({ minMag: 0, region: '全部' }); // 提升到这里
return (
<>
<FilterPanel value={filter} onChange={setFilter} /> {/* 向下:数据 */}
<MapView filter={filter} /> {/* 向下:数据 */}
</>
);
}
// FilterPanel 改条件 → 调 onChange → App 的 filter 变 → MapView 收到新 props 重渲染
这就是第 02 节"props 向下、事件向上"的全景:任何两个组件的通信,本质都是"把 state 提到共同祖先 + 两条单向通道"。React 的数据流因此永远可追踪。
三、Context:跳过中间层
状态提升的代价:数据要经过每一层中间组件转发(prop drilling,"属性钻井")——层深了以后全是没必要的透传。Context 让数据"广播"给下面所有层,中间层不再经手:
// ① 创建 Context(单独文件,如 theme-context.jsx)
import { createContext, useContext, useState } from 'react';
const ThemeContext = createContext(null);
// ② Provider:数据的"广播塔"(放在需要覆盖的子树顶端)
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggle = () => setTheme((t) => (t === 'light' ? 'dark' : 'light'));
return (
<ThemeContext.Provider value={{ theme, toggle }}>
{children}
</ThemeContext.Provider>
);
}
// ③ 自定义 Hook:消费端统一入口(规范用法,报错信息友好)
export function useTheme() {
const ctx = useContext(ThemeContext);
if (!ctx) throw new Error('useTheme 必须在 ThemeProvider 内使用');
return ctx;
}
// main.jsx:包住应用
<ThemeProvider><App /></ThemeProvider>
// 任意深度的组件:不用层层透传,直接用
function ThemeButton() {
const { theme, toggle } = useTheme();
return <button onClick={toggle}>当前:{theme}</button>;
}
四大经典 Context 场景:主题(本节)、当前登录用户、地图实例(把 Leaflet/OL 的 map 对象广播给所有面板组件——第 07 模块的项目直接用)、界面语言。
四、选型边界
| 距离与频率 | 方案 |
|---|---|
| 父子 | props + 回调(第 02 节) |
| 兄弟/少量层级 | 状态提升 |
| 跨多层、低频变化(主题/用户/地图实例) | Context |
| 高频更新的全局状态(大数据、高频交互) | 状态库 Zustand(第 14 节正式讲,先知道) |
⚠️ Context 的坑:value 变化会让所有消费组件重渲染。高频变化的数据塞 Context 会导致大面积无差别刷新——这就是"高频上状态库"的原因。
五、动手跟练:07 · 主题切换
配套文件夹:03-react-nextjs/examples/07-主题切换/(npm i && npm run dev)
步骤:
- 需求:三个互不相邻的组件(顶栏按钮/卡片/页脚文字)同步切换明暗主题——用 Context 一次搞定
- 读代码:ThemeProvider / useTheme 的完整三件套(对照文档第三节逐行看)
- 完成 5 个 TODO:卡片组件接主题(按 theme 换类名)、localStorage 记住选择(初始化读)、再加一个"字号大小"Context(体验多 Context 并存)、故意在 Provider 外用 useTheme 看报错、把主题 state 从 Context 抽回 App 用 props 传一遍对比(体会 prop drilling 的痛)
- 观察重渲染范围:React DevTools(后面装)或 console.log 打点——哪些组件随主题变化重渲染了?
通关标准:
- 三处 UI 主题同步切换,刷新后保持
- 能默写 Context 三件套(createContext / Provider / useContext + 自定义 Hook)
- 能说出 prop drilling 的痛与 Context 的适用边界
六、自测题
- 兄弟组件通信的标准解法?核心动作叫什么?
- Context 三件套是哪三个?各自职责?
- 为什么自定义 Hook(useTheme)里要 throw?
- Context 的性能坑是什么?哪些数据不适合放?
- 地图实例(map 对象)适合放 Context 吗?为什么?
参考答案
- 状态提升到最近公共父级 + props 向下/回调向上。
- createContext(创建通道)、Provider(广播数据,value 变则消费者更新)、useContext(消费)——外加自定义 Hook 封装是行业惯例。
- 防止在 Provider 外误用(拿到 null 而是报出人话错误,比 undefined 深处爆炸好排查)。
- value 一变全体消费者重渲染;高频变化的大数据(如鼠标坐标、大量业务数据)不适合。
- 适合且是经典用法——map 实例创建后引用稳定(低频变化),却需要被工具栏/图层树/状态栏多个深层组件访问。
七、下一步
组件、数据、通信、路由前的最后一块 → 第 08 节:react-router,多页面 SPA 的骨架。
TAGSweb