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.jsx、Child.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 编程时代后,不仅能用官方提供的 useState、useEffect、useContext,还能自己造轮子。

自定义 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

相关推荐
香芋芋圆2 小时前
iD-editor 详解|OSM官方在线编辑器,基于D3.js的轻量地图编辑器
开发语言·javascript·编辑器
晗夜未央3 小时前
任务队列反复失败,先分清重试、阻塞和结果待核对
开发语言·javascript
jktantan4 小时前
每个节点 41 字节:把一棵百万节点树拆进 14 个 typed array
前端·javascript·vue.js·算法·typescript
我命由我123455 小时前
访客系统形式(有用户概念、无用户概念)
java·开发语言·前端·javascript·python·产品运营·产品经理
程序员大阳5 小时前
副队长JavaScript教程(65)-详解JSON:轻量级数据交换格式
javascript·json·序列化·反序列化·stringify·parse
paopaokaka_luck7 小时前
基于springboot+vue的益暖微光公益服务平台(DeepSeek公益助手、善款信息追溯、志愿服务积分闭环、ECharts运营分析、活动状态自动流转)
java·前端·javascript·spring boot·mysql·架构·echarts
栀椩7 小时前
Electron 内嵌页面跨域问题(CORS/PNA)解决全记录
前端·javascript·electron
SEO_juper7 小时前
Chrome 扩展一键查看当前页面 SEO 信息:不用切页面,点一下全出来
前端·javascript·chrome
Highcharts.js7 小时前
CSUN 2026|Highcharts无障碍数据与AI分享
javascript·人工智能·数据分析·highcharts·图表开发
系统集成-鹰翔8 小时前
ERP推单数据库怎么备份:SQLite WAL模式下只复制db文件为什么可能漏任务
javascript·数据库·经验分享·后端