React 组件通信进化史:从 Props 层层搬运到 Context 跨层级直达

技术栈:React 19 + Vite


一、组件通信的四大场景

React 是单向数据流架构,数据只能从父组件流向子组件。在这个前提下,组件之间的通信分为四种关系:

1.1 父子组件通信

最基础的模式 ------ props 传递数据,自定义事件回传

scss 复制代码
父组件 ──props(data)──→ 子组件
父组件 ←──事件回调──── 子组件

父组件把数据通过 props 塞给子组件,子组件需要通知父组件时,调用父组件传下来的回调函数。这是 React 里最自然、最推荐的通信方式。而且因为数据源头在父组件手里,天然实现了共享数据、统一状态管理

1.2 兄弟组件通信

两个平级组件没有直接的通道,必须通过它们共同的父组件中转:

css 复制代码
父组件
 ├── 兄弟A ──回调→ 父组件(state变更) ──props→ 兄弟B

本质还是父子通信的组合 ------ 状态提升(lifting state up)。

1.3 爷孙 / 跨层级通信

这才是真正的痛点。爷组件要给重孙传数据:

scss 复制代码
爷组件
 └── 父组件 (不需要这个数据,但要帮着传)
      └── 子组件 (不需要这个数据,但也要帮着传)
           └── 孙组件 (终于用上了)

原来的那套层层传递,层次太深,搬运工作就太那啥了。

中间每一层都被迫接收并转发自己根本不需要的 props,只是当个"搬运工"。项目越复杂、组件树越深,这种 prop drilling 就越让人烦躁。

1.4 陌生人关系

两个组件在组件树上没有任何血缘关系,既不是父子也不是兄弟。比如侧边栏里的主题切换按钮,和主内容区的文章卡片,它们在不同分支上,但需要共享同一个主题状态。


二、useContext:跨层级直通车

2.1 面对"组件层次比较深"的场景

React 提供了一个原生方案 ------ Context(上下文)。思路很简单:

  • 最外层 放一个 Context.Provider 组件,把数据注入到组件树中
  • 树中的任意后代组件,不管隔了多少层,都能直接**消费(读取)**这份数据
  • 不需要考虑层级,连接上下文就能消费数据

一句话概括:

useContext 可以跨层级直接读取组件上下文数据,省去逐层向下传递 props。

2.2 三步走

步骤 API 做什么 对应文件
createContext 创建上下文,定默认值 ThemeContext.jsx
Provider 组件 包裹组件树,注入数据 App2.jsx
useContext 后代组件消费数据 Page.jsxChild.jsx

第一步:创建上下文

js 复制代码
// src/ThemeContext.jsx
import { createContext } from 'react';

export const ThemeContext = createContext("light");  // 默认值

第二步:Provider 在最外层提供数据

jsx 复制代码
// src/App2.jsx
<ThemeContext.Provider value={theme}>
  <Page />          {/* Page 和它的所有后代都能拿到 theme */}
</ThemeContext.Provider>

Provider 不要求全局 ------ 你想包哪棵子树就包哪棵,灵活得很。value 一变,子树里所有消费了这个 Context 的组件自动重渲染。

第三步:后代任意层级直接消费

jsx 复制代码
// src/components/Page.jsx ------ 第一层
const theme = useTheme();  // 拿到 "light" 或 "dark"

// src/components/Child.jsx ------ 第二层(Page 的子组件)
const theme = useTheme();  // 同样拿到,Page 没有通过 props 传给它

Page 没有给 Child 传 props ,但 Child 自己调用 useContext 直接拿到了主题值。这就是跨层级的威力。


三、自定义 Hooks:把响应式逻辑封装起来

3.1 什么是自定义 Hook?

React 全面进入 Hooks 编程时代后,不仅能用官方提供的 useStateuseEffectuseContext,还能自己造轮子。

自定义 Hook 的约定:

  • 函数名以 use 开头
  • 放在 hooks/ 目录下,属于项目架构层
  • 比普通工具函数多一个能力:能把 响应式状态 (state)和副作用(effect)封装进去,随处复用

3.2 useTheme:封装 useContext

js 复制代码
// src/hooks/useTheme.js
import { ThemeContext } from '../ThemeContext';
import { useContext } from 'react';

export function useTheme() {
  return useContext(ThemeContext);
}

为什么要多此一举包一层?因为消费组件不需要知道背后是 Context 还是 Redux 还是 localStorage。它只认识 useTheme()。将来换数据源,只改 hooks 这一个文件,所有消费方无感。

3.3 useMouse:封装副作用

笔记里写了这个需求:

监听鼠标移动事件,鼠标坐标显示在页面上

------ 抽象出响应式的鼠标坐标,封装到 hooks 目录下

核心思路是把"监听 DOM 事件 → 更新 state"这套响应式流程,抽象成一个可复用的自定义 Hook:

js 复制代码
// src/hooks/useMouse.js
import { useState, useEffect } from 'react';

export const useMouse = () => {
  const [x, setX] = useState(null);
  const [y, setY] = useState(null);

  useEffect(() => {
    document.addEventListener('mousemove', handleMouseMove);
    return () => {
      // 组件卸载后,事件监听不会主动回收 ------ 手动清除
      document.removeEventListener('mousemove', handleMouseMove);
    };

    function handleMouseMove(e) {
      setX(e.clientX);
      setY(e.clientY);
    }
  }, []);

  return { x, y };
};

使用时只需要一行:

jsx 复制代码
// src/App.jsx
const { x, y } = useMouse();
// 组件只管渲染,Hook 负责"监听事件 → 更新 state → 触发重新渲染"

这就是自定义 Hook 的价值:把"监听鼠标移动"这件脏活累活锁在一个函数里,使用时跟调用普通函数一样简单,但它内部是完整响应式的。


四、通信方式全景对比

通信关系 用什么 一句话
父子 props 父传子读,回调回传
兄弟 状态提升到共同父组件 借道父组件中转
爷孙(层次较深) Context + Provider + useContext 跨层级直达,不搬运
陌生人 Context 或全局状态库 共享数据源,各自消费

选择原则:能 props 就 props,层次太深才上 Context。不要一上来就全局 Context ------ 简单的父子关系用 props 可读性更好。


五、总结:两条主线

这个项目其实在讲两件事:

自定义 Hook = 有状态 + 有副作用的可复用函数

把"监听鼠标移动"这种响应式逻辑抽到 hooks 目录,组件只需调用一行。
Context = 跨层级的数据直通车

createContext → Provider 提供 → useContext 消费,不考虑层级,跳过搬运工。

两者结合 ------ 用 Context 共享数据、用自定义 Hook 封装消费逻辑 ------ 就是 React 组件通信的最佳实践。


写于 2026-08-10

相关推荐
BreezeJiang1 小时前
父组件一更新,子组件就必须跟着更新吗?从 memo 走到 useCallback
前端·javascript
胡萝卜术1 小时前
复用与并行:从自定义 Hooks 封装状态逻辑,到 Web Worker 的多线程计算
前端·javascript·面试
huabuyu1 小时前
CLS 总是修不好?因为你只盯着分数,从没拆开看过它
前端·javascript
kisshyshy1 小时前
从多页面到SPA:React Router 路由进阶完全指南
前端·javascript·react.js
今日无bug1 小时前
JS 数据类型 + 内存分配:从 8 种类型到栈堆模型
javascript·数据结构
xiaominlaopodaren1 小时前
three.js地图数学基础(三):实战墨卡托
javascript·gis·three.js
雪碧聊技术1 小时前
力扣 回溯法 | LCR 020. 回文子串
javascript·算法·leetcode
敲敲敲敲暴你脑袋2 小时前
升级啦!纯前端打包下载离线地图
javascript·gis·canvas
SendTomo2 小时前
send.wang私传网:P2P直连传大文件首选工具
javascript·网络·网络协议·webrtc·p2p