useEffect
是 React 中最常用也最容易出错的 Hook 之一。它让函数组件拥有了副作用的能力,比如发起网络请求、操作 DOM、设置定时器等。
但你是否遇到过这些问题:
- 网络请求重复发送?
- 清除逻辑不生效?
- 依赖项总是报错?
今天这篇文章,我们将系统性地掌握 useEffect
,并拆解它背后的机制与常见误区。
一、useEffect 到底是什么?
React 的官网定义:
The
useEffect
Hook lets you perform side effects in function components.
翻译一下:你可以把 useEffect
看作是类组件的 componentDidMount
、componentDidUpdate
和 componentWillUnmount
的合集。
基本语法
scss
jsx
复制编辑
useEffect(() => {
// 副作用逻辑
return () => {
// 清除逻辑
};
}, [依赖项数组]);
二、useEffect 的执行时机
React 在渲染后 执行 useEffect
,具体来说:
- 初次渲染后执行一次(如果依赖数组为空)
- 当依赖项发生变化时,再次执行
- 在下一次执行前(或组件卸载时)调用清除函数(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 函数组件的副作用基石,它虽强大却也容易出错。你需要记住几点:
- 理解它的执行时机(首次 + 依赖变化)
- 正确维护依赖项数组
- 使用清除函数管理副作用
- 避免引用变化带来的误触发
学会之后,你就可以放心优雅地处理各种副作用场景了。