彻底搞懂 useEffect:依赖项、清除函数与常见陷阱

useEffect 是 React 中最常用也最容易出错的 Hook 之一。它让函数组件拥有了副作用的能力,比如发起网络请求、操作 DOM、设置定时器等。

但你是否遇到过这些问题:

  • 网络请求重复发送?
  • 清除逻辑不生效?
  • 依赖项总是报错?

今天这篇文章,我们将系统性地掌握 useEffect,并拆解它背后的机制与常见误区。


一、useEffect 到底是什么?

React 的官网定义:

The useEffect Hook lets you perform side effects in function components.

翻译一下:你可以把 useEffect 看作是类组件的 componentDidMountcomponentDidUpdatecomponentWillUnmount 的合集。

基本语法

scss 复制代码
jsx
复制编辑
useEffect(() => {
  // 副作用逻辑
  return () => {
    // 清除逻辑
  };
}, [依赖项数组]);

二、useEffect 的执行时机

React 在渲染后 执行 useEffect,具体来说:

  1. 初次渲染后执行一次(如果依赖数组为空)
  2. 当依赖项发生变化时,再次执行
  3. 在下一次执行前(或组件卸载时)调用清除函数(return 的函数)

示例:

javascript 复制代码
jsx
复制编辑
useEffect(() => {
  console.log('副作用逻辑执行');

  return () => {
    console.log('清除副作用');
  };
}, [count]);

行为如下:

  • 初次渲染:打印 副作用逻辑执行

  • count 改变时:

    • 先打印 清除副作用
    • 再打印 副作用逻辑执行

三、依赖项数组该怎么写?

这是最容易出错的地方。

✅ 正确写法:

所有在 effect 内部使用到的、在组件作用域中定义的变量和函数都应当出现在依赖数组中。

scss 复制代码
jsx
复制编辑
useEffect(() => {
  fetchData(userId); // 使用了 userId

}, [userId]); // 所以必须写上 userId

⚠️ 错误写法:

scss 复制代码
jsx
复制编辑
useEffect(() => {
  fetchData(userId);
}, []); // 虽然不报错,但 userId 改变不会触发 effect

🧠 ESLint 帮你查问题:

建议开启 eslint-plugin-react-hooks 插件,它会自动提示你遗漏的依赖项。


四、常见 useEffect 场景和写法

1. 请求接口数据

scss 复制代码
jsx
复制编辑
useEffect(() => {
  async function loadData() {
    const res = await fetch('/api/user');
    const data = await res.json();
    setUser(data);
  }
  loadData();
}, []);

✅ 依赖项为空,只执行一次,适用于「页面加载」场景。

2. 监听事件并清除

javascript 复制代码
jsx
复制编辑
useEffect(() => {
  const handleResize = () => {
    console.log(window.innerWidth);
  };

  window.addEventListener('resize', handleResize);
  return () => window.removeEventListener('resize', handleResize);
}, []);

3. 防抖/节流处理(useEffect + setTimeout)

scss 复制代码
jsx
复制编辑
useEffect(() => {
  const timer = setTimeout(() => {
    setDebouncedValue(value);
  }, 300);

  return () => clearTimeout(timer);
}, [value]);

五、useEffect 的常见陷阱

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

scss 复制代码
jsx
复制编辑
useEffect(() => {
  console.log(count); // 使用了 count
}, []); // 却没写 count,容易出错

❌ 2. 无限循环

scss 复制代码
jsx
复制编辑
useEffect(() => {
  setState(1); // 改变 state,触发重新渲染
}, []); // 如果这里写了 [state] 就会死循环

❌ 3. 对象/函数作为依赖

scss 复制代码
jsx
复制编辑
useEffect(() => {
  doSomething(obj); // obj 每次引用不同
}, [obj]); // 导致 effect 总是执行

解决:用 useMemo 或 useCallback 缓存依赖项


六、useEffect vs useLayoutEffect

Hook 触发时机 使用场景
useEffect 渲染后(异步) 异步副作用、网络请求
useLayoutEffect 渲染前(同步) 读写 DOM、同步动画

如果你需要在渲染前立即测量 DOM(如宽高),可以用 useLayoutEffect


七、结语

useEffect 是 React 函数组件的副作用基石,它虽强大却也容易出错。你需要记住几点:

  • 理解它的执行时机(首次 + 依赖变化)
  • 正确维护依赖项数组
  • 使用清除函数管理副作用
  • 避免引用变化带来的误触发

学会之后,你就可以放心优雅地处理各种副作用场景了。

相关推荐
OEC小胖胖4 小时前
去中心化身份:2025年Web3身份验证系统开发实践
前端·web3·去中心化·区块链
Cacciatore->5 小时前
Electron 快速上手
javascript·arcgis·electron
vvilkim5 小时前
Electron 进程间通信(IPC)深度优化指南
前端·javascript·electron
某公司摸鱼前端6 小时前
ES13(ES2022)新特性整理
javascript·ecmascript·es13
ai小鬼头7 小时前
百度秒搭发布:无代码编程如何让普通人轻松打造AI应用?
前端·后端·github
漂流瓶jz7 小时前
清除浮动/避开margin折叠:前端CSS中BFC的特点与限制
前端·css·面试
前端 贾公子7 小时前
在移动端使用 Tailwind CSS (uniapp)
前端·uni-app
散步去海边7 小时前
Cursor 进阶使用教程
前端·ai编程·cursor
清幽竹客7 小时前
vue-30(理解 Nuxt.js 目录结构)
前端·javascript·vue.js
weiweiweb8887 小时前
cesium加载Draco几何压缩数据
前端·javascript·vue.js