返回笔记列表
01 · NOTES / WebGIS 开发

第 07 节 · 组件通信

2026 年 9 月 1 日

第 07 节 · 组件通信

📌 版本信息:React 19.x(2026-08-29 核对) 📚 来源:React 中文文档 · State 管理Context

一、这一节的目标

  1. 掌握"状态提升":兄弟组件通信的标准解法
  2. 掌握 Context:跨层级共享数据的正规通道
  3. 学会自定义 Hook 封装 Context(Provider + useXxx)
  4. 知道通信方案的选型边界(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

步骤:

  1. 需求:三个互不相邻的组件(顶栏按钮/卡片/页脚文字)同步切换明暗主题——用 Context 一次搞定
  2. 读代码:ThemeProvider / useTheme 的完整三件套(对照文档第三节逐行看)
  3. 完成 5 个 TODO:卡片组件接主题(按 theme 换类名)、localStorage 记住选择(初始化读)、再加一个"字号大小"Context(体验多 Context 并存)、故意在 Provider 外用 useTheme 看报错、把主题 state 从 Context 抽回 App 用 props 传一遍对比(体会 prop drilling 的痛)
  4. 观察重渲染范围:React DevTools(后面装)或 console.log 打点——哪些组件随主题变化重渲染了?

通关标准:

  • 三处 UI 主题同步切换,刷新后保持
  • 能默写 Context 三件套(createContext / Provider / useContext + 自定义 Hook)
  • 能说出 prop drilling 的痛与 Context 的适用边界

六、自测题

  1. 兄弟组件通信的标准解法?核心动作叫什么?
  2. Context 三件套是哪三个?各自职责?
  3. 为什么自定义 Hook(useTheme)里要 throw?
  4. Context 的性能坑是什么?哪些数据不适合放?
  5. 地图实例(map 对象)适合放 Context 吗?为什么?

参考答案

  1. 状态提升到最近公共父级 + props 向下/回调向上。
  2. createContext(创建通道)、Provider(广播数据,value 变则消费者更新)、useContext(消费)——外加自定义 Hook 封装是行业惯例。
  3. 防止在 Provider 外误用(拿到 null 而是报出人话错误,比 undefined 深处爆炸好排查)。
  4. value 一变全体消费者重渲染;高频变化的大数据(如鼠标坐标、大量业务数据)不适合。
  5. 适合且是经典用法——map 实例创建后引用稳定(低频变化),却需要被工具栏/图层树/状态栏多个深层组件访问。

七、下一步

组件、数据、通信、路由前的最后一块 → 第 08 节:react-router,多页面 SPA 的骨架。

TAGSweb