React Context 与自定义 Hooks 学习分享

React Context 与自定义 Hooks 学习分享


一、组件通信的痛点

在 React 应用中,组件树往往是多层次的。我们常见的组件关系包括:

  • 父子组件通信:通过 props 实现单向数据流,父组件将数据通过 props 传递给子组件。
  • 兄弟组件通信:需要借助共同的父组件来中转数据。
  • 爷孙组件通信:传统方式是逐层传递 props,层次一深,"搬运工作"就变得繁琐且难以维护。
  • 陌生人组件通信:两个毫无直接关系的组件共享数据,props 传递几乎不可行。

当组件层次比较深时,逐层传递 props 的方式会带来大量冗余代码,这就是 React Context(上下文)要解决的核心问题。


二、useContext ------ 跨层级的数据共享

2.1 什么是 Context?

Context 提供了一种在组件树中共享数据的方式,无需显式地通过组件树逐层传递 props。它的核心思想是:

  • 提供数据上下文 :在组件树的最外层通过 Context.Provider 提供数据。
  • 消费上下文:任意层级的后代组件都可以跨越层级,直接连接上下文、消费数据。

2.2 使用 Context 的三个步骤

第一步:createContext 创建上下文

在 ThemeContext.jsx 中,我们使用 createContext 创建了一个主题上下文:

javascript 复制代码
import { createContext } from 'react';

export const ThemeContext = createContext("light");

createContext("light") 创建了一个上下文对象,"light" 是默认值------当组件树中没有匹配的 Provider 时,会使用这个默认值。

第二步:Provider 组件包裹组件树

Context.Provider 组件用于包裹需要共享数据的那部分组件树,通过 value 属性向下传递数据。Provider 可以放在组件树的任意层级,其内部的所有后代组件都能访问到这个上下文。

第三步:useContext 消费上下文

在 useTheme.js 中,我们使用 useContext 来消费上下文:

javascript 复制代码
import { ThemeContext } from '../ThemeContext';
import { useContext } from 'react';

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

useContext(ThemeContext) 接收一个 context 对象并返回该 context 的当前值。当前值由上层组件中距离最近的 <ThemeContext.Provider> 的 value 属性决定。


三、组件树中的实际消费

在 Page.jsx 和 Child.jsx 中,我们看到了跨层级消费的完整示例:

Page 组件:

javascript 复制代码
import Child from './Child';
import { useTheme } from '../hooks/useTheme';

const Page = () => {
    const theme = useTheme();
    console.log(theme);
    return (
        <>
            Page{theme}
            <br />
            <Child />
        </>
    );
};

Child 组件:

javascript 复制代码
import { useTheme } from '../hooks/useTheme';

function Child() {
    const theme = useTheme();
    console.log(theme);
    return (
        <>
            Child
            <button className={theme}>按钮{theme}</button>
        </>
    );
}

这里 Page 和 Child 是嵌套关系(Page 包含 Child),但它们不需要通过 props 逐层传递 theme 数据。无论组件嵌套多深,只要在同一个 Context.Provider 下,任何层级的组件都可以直接通过 useContext 获取共享数据。 这就是 Context "跨越层级,不考虑层级"的含义。


四、自定义 Hooks ------ 响应式逻辑的封装与复用

4.1 什么是自定义 Hooks?

自定义 Hooks 是以 use 开头的函数,通常放在 hooks 目录下,属于架构层面的抽象。它相比普通函数封装,最大的区别是可以将 React 的响应式状态、副作用等业务逻辑封装进去,从而实现逻辑复用。

4.2 封装 useTheme

useTheme 就是一个典型的自定义 Hook,它将 useContext(ThemeContext) 的调用封装起来:

javascript 复制代码
export function useTheme() {
    return useContext(ThemeContext);
}

这样做的好处是:

  • 模块化抽离 :消费上下文的逻辑从组件中抽离到 hooks 目录,组件只需调用 useTheme() 即可。
  • 多处复用 :在 Provider 内任何层级的组件中,都可以通过 useTheme() 来消费数据。
  • 语义化 :useTheme() 比直接写 useContext(ThemeContext) 更具可读性。

4.3 封装 useMouse ------ 响应式鼠标坐标

在 useMouse.js 中,我们将"监听鼠标移动、获取坐标"这个需求抽象为自定义 Hook:

javascript 复制代码
import { useState, useEffect } from 'react';

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

    useEffect(() => {
        document.addEventListener('mousemove', handleMouseMove);
        return () => {
            // 函数组件卸载后,不会主动回收的
            // 定时器、worker、事件 需要手动回收
            document.removeEventListener('mousemove', handleMouseMove);
        };
        function handleMouseMove(e) {
            setX(e.clientX);
            setY(e.clientY);
        }
    }, []);

    return { x, y };
};

这个 Hook 涉及了几个关键概念:

  1. useState ------ 响应式状态 :x 和 y 是响应式的状态变量。当鼠标移动时,setX 和 setY 更新状态,组件随之重新渲染,页面上显示的坐标实时更新。

  2. useEffect ------ 副作用处理:

    • 通过 document.addEventListener('mousemove', handleMouseMove) 在 document 上注册鼠标移动事件。
    • 返回一个清理函数 ,在组件卸载时执行 document.removeEventListener('mousemove', handleMouseMove) 来移除事件监听。这是因为函数组件卸载后,定时器、Worker、事件监听等不会主动回收,必须手动清理,否则会造成内存泄漏。
    • 依赖数组 [] 表示这个 effect 只在组件挂载时执行一次,卸载时执行清理函数一次。
  3. 封装与复用 :useMouse 将"监听鼠标移动并获取坐标"的完整逻辑封装在一个函数中。任何组件只需调用 const { x, y } = useMouse() 就能获得实时的鼠标坐标,无需关心内部实现细节。

4.4 在 App 中使用 useMouse

在 App.jsx 中,我们看到了 useMouse 的实际使用:

css 复制代码
import { useState, useEffect } from 'react';

function App() {
    const { x, y } = useMouse();
    return (
        <div style={{ height: '100vh', display: 'flex',
            alignItems: 'center', justifyContent: 'center' }}>
            {x && y ? `x: ${x}, y: ${y}` : '鼠标未移动'}
        </div>
    );
}

App 组件通过解构 useMouse() 的返回值获取 x 和 y,然后在页面中央显示鼠标坐标。当鼠标尚未移动时,x 和 y 为 null,显示"鼠标未移动";鼠标移动后实时显示坐标值。


五、总结

这篇文章围绕两个 React 核心概念展开,知识点归纳如下:

Context(上下文)

知识点 说明
createContext 用于创建一个上下文对象,可传入默认值
Context.Provider 上下文提供者组件,包裹组件树并通过 value 属性向下传递数据
useContext Hook,用于在任意层级的后代组件中消费上下文数据
跨层级通信 Context 解决了爷孙组件、陌生人组件之间逐层传递 props 的痛点,实现"跨越层级,不考虑层级"的数据共享

自定义 Hooks

知识点 说明
命名约定 以 use 开头的函数,放在 hooks 目录下
封装能力 相比普通函数,自定义 Hook 可以封装响应式状态(useState)、副作用(useEffect)等 React 特性
逻辑复用 将可复用的业务逻辑抽离成 Hook,多个组件直接调用即可
useState 创建响应式状态变量,状态更新触发组件重新渲染
useEffect 处理副作用(如事件监听),返回的清理函数用于手动回收资源(定时器、事件监听、Worker 等),避免内存泄漏
依赖数组 [] 空数组表示 effect 只在组件挂载和卸载时各执行一次

二者结合

  • 自定义 Hook(如 useTheme)可以将 useContext 的调用封装起来,提供更简洁、语义化的 API。
  • 无论是消费上下文还是监听鼠标事件,自定义 Hooks 都遵循"抽象 → 封装 → 复用"的思路,这正是 React Hooks 编程模式的核心价值。
相关推荐
Setsuna_F_Seiei7 小时前
前端转型 Agent 开发 06 之 Agent Memory 记忆系统(让 Agent 更智能,更懂你)
前端·agent·ai编程
zhangzeyuaaa8 小时前
Ruby 多线程、GVL(GIL)与 Mutex 完全指南
开发语言·前端·ruby
默_笙8 小时前
🌊 向量库和 ES 都查不出"关系",我只好给奶茶建了一张人脉网
前端·javascript
优选资讯8 小时前
标签打印软件怎么对接 Excel 数据
前端·excel
一个风轻云淡9 小时前
GCC 和 GDB命令简单解读
java·linux·前端
u01110267510 小时前
图片处理接口如何统一错误信息 前端提示与后端错误码设计
前端·状态模式
福兮说10 小时前
浏览器里把 iPhone 的 HEIC 转成 JPG:Chrome 解不开、转完大了七成、拍摄时间全丢,六个坑实测
前端·javascript·图像处理·chrome·ios·iphone·heic
libokaifa10 小时前
把语音模型塞进手机:sherpa-onnx 在 Android 上的 ASR / TTS
前端
deli00711 小时前
DLA 枝晶生长实测:5000 个粒子集体停摆之后,分形维数收敛到 1.70
前端
IT_陈寒11 小时前
Redis卡顿的锅,这次真不是大key的错
前端·人工智能·后端