💌 为了把"主题色"传给孙子组件,我翻了五层楼——直到遇见了 useContext

写在前面:今天学了一个让我"拍大腿"的知识------useContext(上下文) 。以前父子通信,爷爷要把数据传给孙子,得一层一层往下递 props:爷爷给爸爸,爸爸给儿子,儿子给孙子......传个数据跟"击鼓传花"似的,中间层的组件明明不用这个数据,也得帮忙"搬运"。老师说:"层次太深,搬运工作就太那啥了。" 于是 React 提供了 Context------数据放在"云端",任何层级的组件都能直接"取用",不用层层递。 我们还用自定义 Hook 把鼠标坐标、主题色都封装了起来,代码瞬间清爽。


一、组件通信的"进化史"

1.1 传统的通信方式

老师说:

"父子组件通信------props 传递数据、自定义事件,共享数据,统一数据状态管理。兄弟关系、爷孙关系、陌生人关系......原来的那套层层传递,层次太深,搬运工作就太那啥了。"

传统的 props 逐层传递:

复制代码
App(爷爷,有 theme 数据)
  ↓ props.theme
Parent(爸爸,只是搬运工!)
  ↓ props.theme
Child(儿子,还是搬运工!)
  ↓ props.theme
GrandChild(孙子,终于用上了)

问题:中间层(Parent、Child)根本不用 theme,但必须帮忙传递------又丑又烦。

老师说:

"组件层次比较深------useContext 上下文:提供数据上下文(最外层 Context.Provider 组件),消费上下文(跨越层级,不考虑层级)。"

1.2 useContext 的解决方案

arduino 复制代码
App(爷爷,提供数据)
  ↓ ThemeContext.Provider 把数据放到"云端"
Parent(不需要传 props 了!)
Child(也不需要传 props 了!)
GrandChild(直接从"云端"拿数据)

数据从"逐层搬运"变成了"云端直取"。


二、useContext 三步走

2.1 第一步:createContext 创建上下文

ThemeContext.jsx

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

// 创建一个 Theme 上下文,默认值是 'light'
export const ThemeContext = createContext('light');

createContext('light') 创建了一个上下文对象,默认值是 'light'

这个上下文就是"云端"的入口------之后 Provider 往里放数据,组件从里取数据。

2.2 第二步:Provider 包裹组件树

App2.jsx

jsx 复制代码
import { useState } from 'react';
import { ThemeContext } from './ThemeContext';
import Page from './components/Page';

function App() {
    const [theme, setTheme] = useState('light');

    return (
        {/* 上下文提供者:Provider 容器 */}
        <ThemeContext.Provider value={theme}>
            <Page />
            <br />
            <button onClick={() => setTheme('dark')}>切换主题</button>
        </ThemeContext.Provider>
    );
}

ThemeContext.Provider 是"数据提供者":

  • value={theme}:把当前的主题值放进上下文。
  • 所有被 Provider 包裹的组件(包括深层子组件)都能访问这个值。
  • 状态一改(setTheme('dark')),所有消费方自动更新。

老师说:

"并不是需要全局,任何地方作为容器使用。默认值 light,可以通过 value 来改变。"

2.3 第三步:useContext 消费数据

Page.jsx

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

const Page = () => {
    const theme = useContext(ThemeContext);  // 直接从"云端"取数据
    console.log(theme);

    return (
        <>
            Page {theme}
            <br />
            <Child />
        </>
    );
};

useContext(ThemeContext) 一步拿到主题值------不管 Page 在第几层,不需要任何 props。

三步总结:

步骤 代码 比喻
① createContext createContext('light') 建好"云端仓库"
② Provider 包裹 <ThemeContext.Provider value={theme}> 把数据放进仓库
③ useContext 消费 useContext(ThemeContext) 从仓库取数据

三、完整链路:切换主题

arduino 复制代码
App 里点"切换主题"
  ↓
setTheme('dark')
  ↓
Provider 的 value 变成 'dark'
  ↓
所有消费方自动重新渲染
  ↓
Page 显示 "Page dark"
Child 显示 "Child dark"

数据实时同步------不用一层层通知,Provider 变了,整个组件树里的消费者全部感知。


四、自定义 Hook:把 useContext 封装成 useTheme

4.1 为什么封装?

老师说:

"React 全面 Hooks 编程,可以使用 React、React-router-dom 等提供的 Hooks,还可以自定义 Hook------use 开头的函数,自己封装的,简单好用。比普通函数的封装,多的地方是可以将 React 响应式、副作用等业务封装进去。"

useTheme.js

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

// 约定以 use 开头
export function useTheme() {
    return useContext(ThemeContext);
}

useTheme()useContext(ThemeContext) 包了一层 ------组件里调用 useTheme() 就能拿到主题,不需要关心 Context 的具体实现。

使用对比:

jsx 复制代码
// 直接用 useContext
const theme = useContext(ThemeContext);

// 用自定义 Hook(更语义化)
const theme = useTheme();

useTheme()useContext(ThemeContext) 可读性更好------一看就知道是"拿主题"的。


五、自定义 Hook 实战:useMouse 跟踪鼠标

5.1 useMouse 封装

useMouse.js

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

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 };
};

export default useMouse;

useMouse 把"鼠标跟踪"的完整逻辑封装成了一个 Hook:

部分 做了什么
useState 响应式鼠标坐标 x、y
useEffect 挂载时监听 mousemove,卸载时移除监听
返回 { x, y } 响应式坐标

重点:useEffect 的清理函数(cleanup) ------removeEventListener 保证组件卸载时移除监听,避免内存泄漏。

5.2 组件里使用

App.jsx

jsx 复制代码
import useMouse from './hooks/useMouse';

function App() {
    const { x, y } = useMouse();  // 一行代码拿到鼠标坐标

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

组件只需要一行 const { x, y } = useMouse()------监听、更新、清理全部由 Hook 内部搞定。

这就是自定义 Hook 的价值:把"响应式 + 副作用"的复杂逻辑封装起来,任何组件一行代码就能复用。


六、总结:useContext + 自定义 Hook

概念 说明
createContext 创建上下文,可设默认值
Provider 数据提供者,value 放数据
useContext 跨层级消费数据,省去逐层传 props
自定义 Hook use 开头的函数,封装响应式/副作用
useTheme 封装 useContext,语义化取主题
useMouse 封装鼠标监听,返回响应式坐标
cleanup 清理 useEffect 返回函数,卸载时清理监听

useContext 解决"跨层级传数据"的问题(不用逐层搬 props),自定义 Hook 解决"逻辑复用"的问题(一行代码用复杂逻辑)。两者组合,React 的代码既简洁又高效。


写在最后

今天最大的收获,是终于不用"击鼓传花"式地传 props 了。爷爷的数据,孙子直接用 useContext 拿,中间层彻底解放。而且自定义 Hook 的封装能力太强了------鼠标坐标、主题色,都是一行代码搞定,复用性拉满。

下次面试官问你:"useContext 和自定义 Hook 怎么用?"

你可以淡定地说:

"useContext 用于跨层级组件通信,解决 props 逐层传递的问题。三步:① createContext(默认值) 创建上下文;② 用 Context.Provider 包裹组件树,通过 value 提供数据;③ 任意层级的组件用 useContext(Context) 直接消费数据------数据变化时所有消费者自动更新。自定义 Hook 是 use 开头的函数,可以封装响应式状态和副作用逻辑,比如 useMouse() 封装鼠标监听(useEffect 监听 + cleanup 清理,返回响应式坐标),useTheme() 封装 useContext(ThemeContext) 让取主题更语义化。两者结合,既解决了跨层传数据,又实现了逻辑复用。"

然后看着面试官满意的表情,心里默念:这波,又稳了。


本文所有代码示例均来自课堂学习资料,真实可运行。

相关推荐
mldong7 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排7 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60618 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角9 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!9 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰9 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi11 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen11 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马12 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu12 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss