当前位置: 首页 > news >正文

理解 React 的 useEffect

文章目录

  • React 的 useEffect
    • 一、什么是副作用(Side Effects)?
    • 二、useEffect 的基本用法
    • 三、依赖数组的三种情况
      • 1. 无依赖数组(每次渲染后都执行, 不推荐)
      • 2. 空依赖数组(仅在挂载时执行一次)
      • 3. 有依赖项(依赖变化时执行)
    • 四、常见应用场景
      • 1. 数据请求:根据参数动态加载数据 ​
      • 2. 事件监听:窗口大小变化时更新状态 ​
      • 3. 定时器:倒计时功能 ​
      • 4. 动画帧(requestAnimationFrame)
      • 5. 本地存储同步
    • 五、清理函数(Cleanup Function)
    • 六、性能优化与注意事项
      • 1. 避免无限循环
      • 2. 依赖项是对象或数组时
      • 3. 按职责拆分副作用
    • 七、常见问题与解决方案
      • 1. 如何在 useEffect 中使用异步函数?
      • 2. 依赖项缺失导致逻辑错误
    • 八、总结与最佳实践

React 的 useEffect

useEffect 是 React Hooks 中最重要的 API 之一,用于处理组件中的副作用(Side Effects),例如数据请求、DOM 操作、订阅事件等。本文将从基础用法、核心原理、常见问题到最佳实践,全面解析 useEffect 的使用技巧。

一、什么是副作用(Side Effects)?

在 React 中,副作用是指那些与组件渲染结果无直接关系,但可能影响其他组件或外部系统的操作。例如:

  • 数据请求(API 调用)
  • 手动修改 DOM
  • 订阅事件(如 WebSocket、键盘事件)
  • 设置定时器

类组件中,副作用通常写在生命周期方法(如 componentDidMount、componentDidUpdate)中。而函数组件通过 useEffect 统一管理副作用。

二、useEffect 的基本用法

useEffect(() => {// 副作用逻辑return () => {/* 清理函数(可选) */}
}, [dependencies])
  • 第一个参数:一个包含副作用逻辑的函数(必填)
  • 第二个参数:依赖数组(可选),用于控制副作用的执行时机。
  • 返回值:清理函数(可选),用于在组件卸载或下次副作用执行前释放资源。

三、依赖数组的三种情况

1. 无依赖数组(每次渲染后都执行, 不推荐)

useEffect(() => {console.log('每次组件更新后执行')
})

​- ​ 行为 ​​:组件每次渲染(包括首次渲染和更新)后都会执行。
​- ​ 风险 ​​:可能导致性能问题或无限循环(如在副作用中修改状态)。

2. 空依赖数组(仅在挂载时执行一次)

useEffect(() => {console.log('仅在挂载时执行一次')
})
  • 行为 ​​:仅在组件首次渲染后执行一次,类似类组件的 componentDidMount。
    ​- ​ 用途 ​​:初始化操作(如请求初始数据、订阅事件)。

3. 有依赖项(依赖变化时执行)

useEffect(() => {console.log('当 count 变化时执行')
}, [count])

​​ - 行为 ​​:首次渲染后执行,后续仅在依赖项 count 变化时执行。
​​ - 关键点 ​​:依赖项必须是基本类型(如数字、字符串)或稳定引用(通过 useMemo/useCallback 包裹的复杂类型)。

四、常见应用场景

1. 数据请求:根据参数动态加载数据 ​

import { useState, useEffect } from 'react'
import axios from 'axios'function UserProfile({ userId }) {const [userData, setUserData] = useState(null)const [loading, setLoading] = useState(false)useEffect(() => {// 定义取消请求的标记let isCancelled = falseconst fetchUserData = async () => {try {setLoading(true)const response = await axios.get(`/api/users/${userId}`)// 仅在组件未卸载时更新状态if (!isCancelled) {setUserData(response.data)setLoading(false)}} catch (error) {if (!isCancelled) {setLoading(false)console.error('Fetch error:', error)}}}fetchUserData()// 清理函数:取消未完成的请求return () => {isCancelled = true}}, [userId]) // 当 userId 变化时重新加载数据return (<div>{loading ? 'Loading...' : userData && <div>{userData.name}</div>}</div>)
}

2. 事件监听:窗口大小变化时更新状态 ​

import { useState, useEffect } from 'react'function WindowSizeTracker() {const [windowSize, setWindowSize] = useState({width: window.innerWidth,height: window.innerHeight})useEffect(() => {// 定义事件处理函数const handleResize = () => {setWindowSize({width: window.innerWidth,height: window.innerHeight})}// 添加监听window.addEventListener('resize', handleResize)// 清理函数:移除监听return () => {window.removeEventListener('resize', handleResize)}}, []) // 空依赖数组:仅挂载时添加一次监听return (<div>Window Size: {windowSize.width}px x {windowSize.height}px</div>)
}

3. 定时器:倒计时功能 ​

import { useState, useEffect } from 'react'function CountdownTimer({ initialSeconds }) {const [seconds, setSeconds] = useState(initialSeconds)useEffect(() => {// 定义定时器const timer = setInterval(() => {setSeconds((prev) => {if (prev <= 1) {clearInterval(timer) // 倒计时结束清除定时器return 0}return prev - 1})}, 1000)// 清理函数:组件卸载时清除定时器return () => clearInterval(timer)}, []) // 空依赖数组:只在挂载时启动定时器return <div>Time Left: {seconds} seconds</div>
}

4. 动画帧(requestAnimationFrame)

import { useState, useEffect, useRef } from 'react'function AnimationBox() {const [position, setPosition] = useState(0)const requestRef = useRef() // 保存动画帧 IDconst animate = () => {setPosition((prev) => (prev >= 100 ? 0 : prev + 1))requestRef.current = requestAnimationFrame(animate) // 递归调用}useEffect(() => {requestRef.current = requestAnimationFrame(animate)// 清理函数:取消动画帧return () => cancelAnimationFrame(requestRef.current)}, []) // 空依赖数组:只在挂载时启动动画return (<div style={{ transform: `translateX(${position}px)` }}>Moving Box</div>)
}

5. 本地存储同步

import { useState, useEffect } from 'react'function ThemeSwitcher() {const [theme, setTheme] = useState(() => {// 从 localStorage 读取初始值const savedTheme = localStorage.getItem('theme')return savedTheme || 'light'})useEffect(() => {// 当 theme 变化时,同步到 localStoragelocalStorage.setItem('theme', theme)}, [theme]) // 依赖 theme,变化时触发return (<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Switch to {theme === 'light' ? 'Dark' : 'Light'} Theme</button>)
}

五、清理函数(Cleanup Function)

清理函数在以下时机执行:

  • 组件卸载时(类似 componentWillUnmount)。
  • 下次副作用执行前(依赖项变化时)。

示例:取消订阅

useEffect(() => {const subscription = eventEmitter.subscribe(() => {/* ... */})return () => subscription.unsubscribe()
}, [])

六、性能优化与注意事项

1. 避免无限循环

在副作用中直接修改依赖项会导致无限循环:

// ❌ 错误:每次更新后修改 count,触发重新渲染
useEffect(() => {setCount(count + 1)
}, [count])

2. 依赖项是对象或数组时

如果依赖项是对象或数组,即使内容相同,引用变化也会触发副作用:

// ❌ 可能意外触发
const config = { enabled: true };
useEffect(() => { ... }, [config]);// ✅ 用 useMemo 稳定引用
const config = useMemo(() => ({ enabled: true }), []);

3. 按职责拆分副作用

// 拆分数据请求和事件监听
useEffect(() => {/* 请求数据 */
}, [])
useEffect(() => {/* 监听事件 */
}, [])

七、常见问题与解决方案

1. 如何在 useEffect 中使用异步函数?

不能直接将 useEffect 的回调设为 async,但可以在内部定义异步函数:

useEffect(() => {const fetchData = async () => {const result = await axios.get(url)setData(result)}fetchData()
}, [url])

2. 依赖项缺失导致逻辑错误

启用 eslint-plugin-react-hooks 规则,确保依赖项完整。

八、总结与最佳实践

  1. 明确依赖项 ​​:始终填写依赖数组,避免遗漏。
  2. 拆分副作用 ​​:不同逻辑使用多个 useEffect。
  3. 及时清理资源 ​​:防止内存泄漏。
  4. 稳定引用 ​​:使用 useCallback 和 useMemo 处理复杂依赖。

通过合理使用 useEffect,可以写出更清晰、健壮的 React 组件。

http://www.xdnf.cn/news/47647.html

相关文章:

  • 代理模式(Proxy Pattern)
  • 返回内容协商,@ResponseBody 注解
  • C++面试题集合(附答案)
  • [Windows]_[VS2017]_[如何进行远程调试程序]
  • 计算机视觉与深度学习 | 工业视觉缺陷检测如何检小缺陷?背景概述,原理,检测难点,常用的检测算法,算法评估指标,新项目算法选择,算法部署
  • 【Oracle专栏】Oracle中的虚拟列
  • Linux文件时间戳详解:Access、Modify、Change时间的区别与作用
  • PCA——主成分分析数学原理及代码
  • 小迪抓包技术算法加密(6-9天)
  • Dify部署内网时遇到的代理问题及解决办法
  • 【Python爬虫详解】第一篇:Python爬虫入门指南
  • B+树节点与插入操作
  • git清理--解决.git文件过大问题
  • 基于蒙特卡洛模拟与时间序列分析的美的集团财务预测模型研究
  • DeepSeek 助力 Vue 开发:打造丝滑的二维码生成(QR Code)
  • 常用的验证验证 onnxruntime-gpu安装的命令
  • WIN10重启开机不用登录,直接进入桌面
  • Java【网络原理】(4)HTTP协议
  • Redis 的几种数据类型
  • 【Linux我做主】GDB调试工具完全指南
  • Pandas数据可视化
  • UE5 UI 教程系列全集
  • 从入门到精通汇编语言 第六章(中断及外部设备操作)
  • C++ 相关系统软件简介与学习方法【最水的一期】
  • An Improved Fusion Scheme for Multichannel Radar Forward-Looking Imaging论文阅读
  • 代码随想录算法训练营第二十二天(补)
  • Java8-遍历list取出两个字段重新组成list集合
  • linux常用基础命令_最新版
  • 【AI 加持下的 Python 编程实战 2_07】第七章:基于 Copilot 完整演示问题分解能力在实际问题中的落地应用
  • 虚拟教学助理应用系统设计框架