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.jsxChild.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 ------ 响应式状态xy 是响应式的状态变量。当鼠标移动时,setXsetY 更新状态,组件随之重新渲染,页面上显示的坐标实时更新。

  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() 的返回值获取 xy,然后在页面中央显示鼠标坐标。当鼠标尚未移动时,xynull,显示"鼠标未移动";鼠标移动后实时显示坐标值。


五、总结

这篇文章围绕两个 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 编程模式的核心价值。
相关推荐
胡萝卜术1 小时前
声明式与命令式的边界:从鉴权路由守卫到 useRef 的引用哲学
前端·javascript·面试
Python私教1 小时前
如意 Django CRM 容器化实战:后端、前端、数据库、Redis 的协同启动逻辑
前端·数据库·django
默_笙1 小时前
🍉 我把《天龙八部》喂给了 AI,问它"段誉会什么武功?"答案让我惊呆了
前端·javascript
东风破_2 小时前
React 自定义 Hook 实战:封装鼠标坐标与副作用清理
前端
bonechips2 小时前
useRef + Web Worker:React 中的多线程计算
前端·javascript
Sterting2 小时前
第 10 节:异步请求与 Axios —— 前端与后端
前端
Coder_Ke2 小时前
源码没错,API 却“失踪”了:微信开发者工具的一次作用域背刺
前端
渣波2 小时前
从“迷路”到“瞬移”:彻底搞懂 React Router 的 `useLocation` 与 `useNavigate`
前端·javascript
申君健24864182772022 小时前
WebGPU 开发入门:从设备初始化到渲染一帧
前端