深入理解 React useContext:跨层级组件通信的核心利器

文章目录

    • 一、前言:什么是"上下文"?
    • 二、组件通信的四种关系
    • [三、当组件层次很深时------useContext 登场](#三、当组件层次很深时——useContext 登场)
      • [3.1 它解决了什么问题?](#3.1 它解决了什么问题?)
      • [3.2 核心角色:Provider 与 Consumer](#3.2 核心角色:Provider 与 Consumer)
    • [四、useContext 三步走(实战完整代码)](#四、useContext 三步走(实战完整代码))
      • [第一步:createContext 创建上下文](#第一步:createContext 创建上下文)
      • [第二步:Provider 包裹组件树](#第二步:Provider 包裹组件树)
      • [第三步:useContext 消费上下文](#第三步:useContext 消费上下文)
    • [五、自定义 Hooks:把上下文消费封装起来](#五、自定义 Hooks:把上下文消费封装起来)
      • [5.1 为什么需要自定义 Hook?](#5.1 为什么需要自定义 Hook?)
      • [5.2 封装 useTheme Hook](#5.2 封装 useTheme Hook)
      • [5.3 自定义 Hook 的本质](#5.3 自定义 Hook 的本质)
    • 六、综合实战:监听鼠标移动,坐标实时显示
      • [6.1 需求分析](#6.1 需求分析)
      • [6.2 完整代码实现](#6.2 完整代码实现)
      • [6.3 在组件中使用](#6.3 在组件中使用)
      • [6.4 代码执行流程解析](#6.4 代码执行流程解析)
      • [6.5 和 Context 的关系](#6.5 和 Context 的关系)
    • 七、全文总结
      • [7.1 知识路径回顾](#7.1 知识路径回顾)
      • [7.2 核心知识点复盘](#7.2 核心知识点复盘)
      • [7.3 常见问题 / 避坑指南](#7.3 常见问题 / 避坑指南)

一、前言:什么是"上下文"?

在 React 开发中,"上下文"(Context)这个词并不陌生。你可以把它想象成一颗组件树的"全局广播站"------某个组件在上面"喊一声"(提供数据),树中任意深度的子组件都能"听到"(消费数据),而不需要中间每一层组件帮忙传话。

但在理解 Context 之前,我们有必要先回顾一下 React 中组件通信的各种场景,这样才能真正明白 为什么需要 useContext


二、组件通信的四种关系

React 应用由组件构成,组件之间必然要传递数据。根据组件之间的层级关系,通信方式可以归纳为以下四种。

1 父子组件通信

这是 React 中最基础、最常见的通信方式,核心是单向数据流

复制代码
数据从父组件流向子组件,通过 props 传递。
子组件不能直接修改父组件的数据,而是通过父组件传递的回调函数来通知父组件。

代码示例:父传子 + 子通知父

js 复制代码
// 父组件
function Parent() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h2>父组件 ------ 当前计数:{count}</h2>
      {/* ① 通过 props 把数据传给子组件 */}
      {/* ② 同时把修改数据的方法也传给子组件 */}
      <Child count={count} onIncrement={() => setCount(count + 1)} />
    </div>
  );
}

// 子组件
function Child({ count, onIncrement }) {
  return (
    <div>
      <p>子组件接收到:{count}</p>
      {/* 点击时调用父组件传下来的回调 */}
      <button onClick={onIncrement}>+1</button>
    </div>
  );
}

解析 :父组件通过 props 向下传递 count 数据,同时把 onIncrement 回调函数一并传下去,子组件点击按钮时调用这个回调,父组件更新 state,React 自动重新渲染。这就是典型的单向数据流 + 状态提升模式。

这种模式的优点在于:数据流向清晰、易于追踪调试,适合绝大多数简单场景。

兄弟组件通信

两个组件有同一个父组件,它们之间需要共享数据时,做法是把共享状态提升到它们共同的父组件中

兄弟组件之间不直接通信,而是通过"最近共同祖先"作为中转站。这和父子通信本质上是一种模式。

爷孙 / 跨层级组件通信

这是问题开始暴露的地方。假设组件层级如下:

复制代码
爷爷(GrandParent)
 └── 爸爸(Parent)
      └── 儿子(Child)
           └── 孙子(GrandChild)

如果爷爷组件的数据要传给孙子,用 props 就得这样写:

js 复制代码
function GrandParent() {
  const [theme, setTheme] = useState('dark');
  return <Parent theme={theme} />;
}

function Parent({ theme }) {
  // 这层根本不关心 theme,纯粹当"搬运工"
  return <Child theme={theme} />;
}

function Child({ theme }) {
  // 继续搬运...
  return <GrandChild theme={theme} />;
}

function GrandChild({ theme }) {
  // 终于用到 theme 了!
  return <div>当前主题:{theme}</div>;
}

这就是经典的"Props 透传"(Prop Drilling)问题:中间组件被迫接收并转发自己根本不需要的 props,层次越深,搬运工作就越繁琐、越容易出错。一个中间组件改动了 props 名称,整个链条都要跟着改。

陌生人组件通信

两个组件在组件树中毫无关系(不同的页面、不同的路由分支),它们之间的数据共享就需要借助全局状态管理方案 (如 Redux、Zustand)或 React Context 来实现。对于"不引入第三方库,又想全局共享数据"的场景,Context 就是 React 提供的原生解决方案。


三、当组件层次很深时------useContext 登场

3.1 它解决了什么问题?

上面提到的 Props 透传问题,随着项目变大,组件层级越来越深,"搬运 props" 会变成一种折磨:

  • 中间组件被迫接收无关 props,代码变得臃肿
  • 需求变更时(比如要加一个新 prop),每一层中间组件都得改
  • 组件复用性变差------中间组件和特定 props 强耦合

useContext 的思路很简单:开一个"跨层级的数据通道",让数据提供者和数据消费者直接对话,中间组件完全不需要参与。

3.2 核心角色:Provider 与 Consumer

Context 机制中有两个关键角色:

角色 作用 比喻
Provider(提供者) 包裹在组件树最外层,负责"广播"数据 广播电台
Consumer(消费者) 组件树中任意深度的组件,负责"接收"数据 收音机

Provider 只管发,Consumer 只管收,中间隔了多少层完全不重要。


四、useContext 三步走(实战完整代码)

下面通过一个真实的主题切换案例,演示 useContext 的完整用法。代码来自项目实战,可直接运行。

第一步:createContext 创建上下文

js 复制代码
// ThemeContext.jsx
// 创建一个主题上下文,为深层次的组件树提供主题共享数据
import { createContext } from 'react';

// createContext('light') 创建一个上下文对象
// 参数 'light' 是默认值 ------ 当组件树中没有 Provider 时,就使用这个值
export const ThemeContext = createContext("light");

关键解析

  • createContext(defaultValue) 接收一个默认值 作为参数。这个默认值只有在组件树中找不到对应 Provider 时才会生效。
  • 返回的是一个 Context 对象,它身上自带 ProviderConsumer 两个属性。
  • 通常将 Context 对象单独放在一个文件中,方便多处引入。

第二步:Provider 包裹组件树

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

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

  return (
    // ThemeContext.Provider 是上下文的"提供者"容器
    // 注意:Provider 不是只能放在全局!任何组件都可以作为局部容器使用
    // value 属性用于向子树"广播"数据,覆盖 createContext 的默认值
    <ThemeContext.Provider value={theme}>
      {/* Page 及其所有子孙组件,都能直接读取到 theme */}
      <Page />
      {/* 点击按钮切换主题:在 'light' 和 'dark' 之间切换 */}
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        切换主题
      </button>
    </ThemeContext.Provider>
  );
}

export default App;

关键解析

  • <ThemeContext.Provider value={...}> 是提供数据的方式。value 是你想共享的任何数据------可以是字符串、数字、对象、甚至是另一个 state 的函数。
  • Provider 不一定要放在应用最外层,它可以放在任意位置,只有它包裹的那部分组件树才能消费这个上下文。这是一种"局部全局"的设计。
  • value 的值变化时,所有消费该上下文的组件都会自动重新渲染,不需要手动通知。

第三步:useContext 消费上下文

任何被 Provider 包裹的组件(无论嵌套多深),都可以直接使用 useContext 读取上下文数据:

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

const Page = () => {
  // 一行代码,直接拿到主题数据 ------ 不需要从父组件接收 props!
  const theme = useContext(ThemeContext);
  console.log(theme); // 'light' 或 'dark'

  return (
    <>
      <p>Page 组件 ------ 当前主题:{theme}</p>
      <br />
      {/* Child 组件里也消费同一份上下文,同样不需要传 props */}
      <Child />
    </>
  );
};

export default Page;

再看 Child 组件,它甚至更深一层,同样可以消费上下文:

js 复制代码
// components/Child.jsx
import { useTheme } from '@/hooks/useTheme';

function Child() {
  // 使用自定义 hook 消费上下文(推荐方式,下文详述)
  const theme = useTheme();
  console.log(theme);

  return (
    <>
      <p>Child 组件</p>
      <button className={theme}>按钮 ------ {theme}</button>
    </>
  );
}

export default Child;

效果总结 :App 中点击按钮切换 theme state → Provider 的 value 变化 → Page 和 Child 自动用新 theme 重新渲染。整个过程,App 没有向 Page 传任何 props,Page 也没有向 Child 传 props。跨越层级,直接通信。


五、自定义 Hooks:把上下文消费封装起来

5.1 为什么需要自定义 Hook?

上面的示例中,Page.jsx 这样消费上下文:

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

const theme = useContext(ThemeContext);

这样写完全可以工作,但存在两个问题:

  1. 每个消费组件都要同时引入 ThemeContextuseContext,代码有重复
  2. 如果哪天 Context 的结构变了(比如 key 名改了),所有消费组件都要改

更好的做法是:useContext 调用封装成自定义 Hook,消费者只需要调用这个 Hook 即可。

5.2 封装 useTheme Hook

js 复制代码
// hooks/useTheme.js
// React 全面进入 Hooks 编程时代,可以使用 React、react-router-dom 等提供的 Hooks
// 还可以自定义 Hook ------ 以 use 开头的函数,自己封装的,简单好用
// 比普通函数封装更强的地方:可以把 React 的响应式、副作用等逻辑一并封装进去
// 在 Provider 包裹的任何层级组件中,多处消费数据,模块化抽离放到 hooks 目录下

import { ThemeContext } from '@/ThemeContext';
import { useContext } from 'react';

// 约定:自定义 Hook 必须以 use 开头
export function useTheme() {
  // 内部封装了 useContext 调用,对外暴露的是一个干净的接口
  return useContext(ThemeContext);
}

消费方使用就变得非常简洁:

js 复制代码
// 只需要引入 useTheme 即可,不需要知道 ThemeContext 的存在
import { useTheme } from '@/hooks/useTheme';

function Child() {
  const theme = useTheme(); // 干净利落
  // ...
}

自定义 Hook 的优势

  • 封装细节:消费者不需要知道底层是 Context 还是其他方式,换了实现也不影响使用方
  • 统一管理:所有对 ThemeContext 的访问收敛到一个函数中,方便维护
  • 可测试性:可以单独为 Hook 编写测试
  • 模块化 :放在 hooks/ 目录下,属于基础设施层 / 架构层代码

5.3 自定义 Hook 的本质

自定义 Hook 本质上就是use 开头的普通 JavaScript 函数。和普通函数的区别在于:

对比维度 普通函数 自定义 Hook
能否调用 React Hooks(useState、useEffect 等) ✗ 不能 ✓ 可以
能否封装响应式数据逻辑 ✗ 不能 ✓ 可以
命名规范 无强制要求 必须以 use 开头
适用场景 工具函数、纯逻辑 状态、副作用、Context 等 React 特性

六、综合实战:监听鼠标移动,坐标实时显示

掌握了 Context 和自定义 Hook,我们来看一个不涉及 Context、但同样体现自定义 Hook 价值的实战案例------封装鼠标坐标追踪。

6.1 需求分析

目标:在页面上实时显示鼠标的 X、Y 坐标。

思路抽象 :把"追踪鼠标坐标"这段逻辑抽象成一个响应式的数据源 ------useMouse Hook。哪个组件需要鼠标坐标,直接调用它即可,不需要在每个组件里写一遍事件监听的代码。

6.2 完整代码实现

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

export const useMouse = () => {
  // ① 用两个 state 分别存储鼠标的 x、y 坐标
  const [x, setX] = useState(null);
  const [y, setY] = useState(null);

  // ② useEffect 监听副作用:绑定/解绑事件
  useEffect(() => {
    // ③ 事件处理函数:鼠标移动时更新 state
    function handleMouseMove(e) {
      setX(e.clientX);  // clientX 是鼠标相对于浏览器窗口的水平坐标
      setY(e.clientY);  // clientY 是鼠标相对于浏览器窗口的垂直坐标
    }

    // ④ 绑定事件监听器
    document.addEventListener('mousemove', handleMouseMove);

    // ⑤ 清理函数:组件卸载时自动移除事件监听
    //    定时器、Web Worker、事件监听等资源,React 不会主动回收
    //    必须手动清理,否则会造成内存泄漏
    return () => {
      document.removeEventListener('mousemove', handleMouseMove);
    };
  }, []); // 空依赖数组,只在组件挂载时执行一次

  // ⑥ 返回坐标数据,供组件使用
  return { x, y };
};

6.3 在组件中使用

js 复制代码
// App.jsx
import { useMouse } from './hooks/useMouse';

function App() {
  // 使用自定义 Hook 获取鼠标坐标 ------ 代码非常简洁!
  const { x, y } = useMouse();

  return (
    <div style={{
      height: '100vh',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center'
    }}>
      {/* x 和 y 为 null 表示鼠标还没移动过 */}
      {x && y ? `当前鼠标坐标:x: ${x}, y: ${y}` : '鼠标未移动'}
    </div>
  );
}

export default App;

6.4 代码执行流程解析

复制代码
组件首次渲染 → useMouse() 执行
                → useState(null) 创建 x、y 状态
                → useEffect 注册 mousemove 监听器
                → 返回 {x: null, y: null}
                → 组件渲染 "鼠标未移动"

用户移动鼠标 → handleMouseMove 被调用
                → setX / setY 更新状态
                → React 检测到状态变化
                → 组件重新渲染,显示最新坐标

组件卸载     → useEffect 的清理函数执行
                → removeEventListener 移除监听器
                → 避免内存泄漏

6.5 和 Context 的关系

注意:useMouse 这个 Hook 没有用到 Context ,它展示的是自定义 Hook 的另一个重要用途------封装副作用(useEffect)和响应式数据(useState)。
#mermaid-svg-5BbMwOuOGK4zLdVO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5BbMwOuOGK4zLdVO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5BbMwOuOGK4zLdVO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5BbMwOuOGK4zLdVO .error-icon{fill:#552222;}#mermaid-svg-5BbMwOuOGK4zLdVO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5BbMwOuOGK4zLdVO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5BbMwOuOGK4zLdVO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5BbMwOuOGK4zLdVO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5BbMwOuOGK4zLdVO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5BbMwOuOGK4zLdVO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5BbMwOuOGK4zLdVO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5BbMwOuOGK4zLdVO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5BbMwOuOGK4zLdVO .marker.cross{stroke:#333333;}#mermaid-svg-5BbMwOuOGK4zLdVO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5BbMwOuOGK4zLdVO p{margin:0;}#mermaid-svg-5BbMwOuOGK4zLdVO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-5BbMwOuOGK4zLdVO .cluster-label text{fill:#333;}#mermaid-svg-5BbMwOuOGK4zLdVO .cluster-label span{color:#333;}#mermaid-svg-5BbMwOuOGK4zLdVO .cluster-label span p{background-color:transparent;}#mermaid-svg-5BbMwOuOGK4zLdVO .label text,#mermaid-svg-5BbMwOuOGK4zLdVO span{fill:#333;color:#333;}#mermaid-svg-5BbMwOuOGK4zLdVO .node rect,#mermaid-svg-5BbMwOuOGK4zLdVO .node circle,#mermaid-svg-5BbMwOuOGK4zLdVO .node ellipse,#mermaid-svg-5BbMwOuOGK4zLdVO .node polygon,#mermaid-svg-5BbMwOuOGK4zLdVO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5BbMwOuOGK4zLdVO .rough-node .label text,#mermaid-svg-5BbMwOuOGK4zLdVO .node .label text,#mermaid-svg-5BbMwOuOGK4zLdVO .image-shape .label,#mermaid-svg-5BbMwOuOGK4zLdVO .icon-shape .label{text-anchor:middle;}#mermaid-svg-5BbMwOuOGK4zLdVO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5BbMwOuOGK4zLdVO .rough-node .label,#mermaid-svg-5BbMwOuOGK4zLdVO .node .label,#mermaid-svg-5BbMwOuOGK4zLdVO .image-shape .label,#mermaid-svg-5BbMwOuOGK4zLdVO .icon-shape .label{text-align:center;}#mermaid-svg-5BbMwOuOGK4zLdVO .node.clickable{cursor:pointer;}#mermaid-svg-5BbMwOuOGK4zLdVO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5BbMwOuOGK4zLdVO .arrowheadPath{fill:#333333;}#mermaid-svg-5BbMwOuOGK4zLdVO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5BbMwOuOGK4zLdVO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5BbMwOuOGK4zLdVO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5BbMwOuOGK4zLdVO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5BbMwOuOGK4zLdVO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5BbMwOuOGK4zLdVO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5BbMwOuOGK4zLdVO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5BbMwOuOGK4zLdVO .cluster text{fill:#333;}#mermaid-svg-5BbMwOuOGK4zLdVO .cluster span{color:#333;}#mermaid-svg-5BbMwOuOGK4zLdVO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5BbMwOuOGK4zLdVO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5BbMwOuOGK4zLdVO rect.text{fill:none;stroke-width:0;}#mermaid-svg-5BbMwOuOGK4zLdVO .icon-shape,#mermaid-svg-5BbMwOuOGK4zLdVO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5BbMwOuOGK4zLdVO .icon-shape p,#mermaid-svg-5BbMwOuOGK4zLdVO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5BbMwOuOGK4zLdVO .icon-shape .label rect,#mermaid-svg-5BbMwOuOGK4zLdVO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5BbMwOuOGK4zLdVO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5BbMwOuOGK4zLdVO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5BbMwOuOGK4zLdVO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 副作用场景
useMouse
封装 useState + useEffect

复用状态逻辑
Context 场景
useTheme
封装 useContext

跨组件共享数据

两者都是自定义 Hook,只是封装的 React 特性不同:

  • Context 型 Hook(useTheme):解决"跨层级数据共享"
  • 副作用型 Hook(useMouse):解决"响应式逻辑复用"

七、全文总结

7.1 知识路径回顾

本文从 React 组件通信的实际痛点出发,沿着以下路径逐步深入:

复制代码
组件通信四种关系 → Props 透传问题 → Context 机制原理
→ createContext / Provider / useContext 三步法
→ 自定义 Hook 封装(useTheme)→ 副作用封装(useMouse)

核心结论 :useContext 不是用来替代 props 的,而是在 props 传递变得繁琐时的一种优雅补充。对于浅层通信,props 足够且更直观;对于跨层级共享数据,Context 是最合适的原生方案。

7.2 核心知识点复盘

知识点 一句话总结
单向数据流 React 数据从父到子通过 props 流动,是 React 的核心设计原则
Props 透传 中间组件被迫转发不关心的 props,层次深时难以维护
createContext 创建一个上下文容器,参数为默认值
Provider 包裹组件树,通过 value 属性向下广播数据
useContext 在任意深度的子组件中直接读取上下文数据
自定义 Hook use 开头的函数,可封装 React Hooks 进行逻辑复用
useEffect 清理 事件监听、定时器等资源必须在清理函数中手动回收

7.3 常见问题 / 避坑指南

Q1:什么时候用 Context,什么时候用状态管理库(Redux / Zustand)?

场景 推荐方案
少量全局数据(主题、语言、用户信息) Context 足够
频繁更新的状态(如动画帧、输入框实时值) 不适合 Context,频繁变化会导致整个子树重渲染
复杂的状态逻辑、中间件、调试工具 状态管理库(Redux、Zustand)
不想引入第三方依赖 Context + useReducer 组合

Q2:Provider 的 value 变化时,所有消费组件都会重新渲染吗?

会。Provider 的 value 一旦变化,所有调用 useContext 的组件都会重新渲染,这是 Context 的性能代价。如果你的数据中有一部分变化频繁、另一部分相对稳定,建议拆分成多个 Context,让不相关的消费者免受影响。

Q3:自定义 Hook 为什么必须以 use 开头?

这是 React 的约定 (也是 ESLint 规则的强制要求)。React 通过函数名是否以 use 开头来判断这个函数是否是一个 Hook,从而对 Hook 的调用规则(不能放在条件语句、循环中)进行静态检查。

Q4:useEffect 的清理函数什么时候执行?

两种情况下执行:

  1. 组件卸载时------这是最常见的情况,用于清理事件监听、定时器等
  2. 依赖项变化、effect 重新执行之前------React 会先运行上一次 effect 的清理函数,再执行新的 effect
相关推荐
lllsure1 小时前
Vue&React Router
前端·vue.js·react.js
weixin_431600441 小时前
为什么 Agent REPL 要上 Ink:好处、用法与内部设计
前端·学习·ai·agent·ai编程
IT_陈寒2 小时前
Java 8的stream让我debug了一整天,气笑了
前端·人工智能·后端
小黑技术栈2 小时前
web前端基础到入门——14day
前端·数据库·oracle
谷哥的小弟2 小时前
TypeScript类型断言
前端·javascript·typescript
weixin_BYSJ19872 小时前
springboot医疗信息管理系统---附源码17465
java·javascript·spring boot·python·django·flask·php
智塑未来2 小时前
鸿蒙系统小红书隐私保护——应用锁开启指南
服务器·前端·harmonyos
zhongjunyao3 小时前
Spec-Driven Development (SDD): The Definitive 2026 Guide
javascript·spring boot·python·eclipse·代理模式
东风破_11 小时前
React 受控组件与非受控组件:从一个输入框讲清表单数据流
前端