深入理解 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 对象,它身上自带 Provider 和 Consumer 两个属性。
  • 通常将 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. 每个消费组件都要同时引入 ThemeContext 和 useContext,代码有重复
  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
相关推荐
传人once11 小时前
页面中心圆圈放大效果如何写
开发语言·javascript·ecmascript
用户0333074139512 小时前
TypeScript using 与 AsyncDisposableStack:在真实服务端代码中协调多资源清理
前端
daisychey12 小时前
用 Playwright 做多平台内容分发,我踩过的 8 个坑
前端
IT_陈寒13 小时前
为什么你应该学习JavaScript?
前端·人工智能·后端
大龄秃头程序员13 小时前
iOS冷启动监控Demo
前端
光影少年13 小时前
为什么 JavaScript 中 0.1 + 0.2 !== 0.3,如何让其相等?
前端·javascript·算法
掘金酱14 小时前
[稀土掘金 × 火山引擎] AI用量周榜冲刺赛|获奖名单公示
前端·人工智能·后端
泡泡oO14 小时前
“如果你还在用Superpowers,那我不要和你说话”
前端·后端·全栈
维克兜率天14 小时前
趋势过滤:避免被“假突破“反复打脸
前端
8年区块链老兵15 小时前
一篇文章教会你:用区块高度读懂比特币链上的每一笔交易
前端