React useEffect 不传第二个参数会怎么样
FreeGuideOnline
最新
2026-07-05
React useEffect 不传第二个参数会发生什么?
在 React 函数组件中,useEffect 是处理副作用的核心 Hook。它的调用方式直接影响组件的执行时机和性能。当你完全省略第二个参数时,会触发一种特殊的行为,这往往是导致无限循环或性能问题的常见原因。
1. 参数回顾:三种依赖项传递方式
在讨论不传第二个参数之前,先回顾 useEffect 的三种基本写法:
| 写法 | 依赖项参数 | 副作用执行时机 |
|---|---|---|
useEffect(() => { ... }) |
不传第二个参数 | 每次渲染后都会执行 |
useEffect(() => { ... }, []) |
传递空数组 | 仅在组件挂载时执行一次 |
useEffect(() => { ... }, [dep]) |
传递依赖数组 | 仅在 dep 变化时执行(含挂载) |
2. 不传第二个参数:每次渲染后都运行
当你不传递第二个参数(即完全不写依赖数组)时,useEffect 中的回调函数会在每一次组件渲染完成后被调用。
useEffect(() => {
console.log('副作用执行,当前渲染次数:', renderCount);
});
这等同于在类组件中没有添加任何条件,直接在 componentDidMount 和 componentDidUpdate 里都执行相同的逻辑。
- 挂载时:组件第一次渲染到 DOM 后,回调执行一次。
- 更新时:每次因为 state 或 props 变化导致重新渲染后,回调都会再次执行。
3. 为什么会导致无限循环?
这是初学者最常踩的坑。如果你在副作用中更新了 state,且该 state 的变化会触发组件的重新渲染,那么就会陷入死亡循环:
function Counter() {
const [count, setCount] = useState(0);
// 🚨 危险:不传依赖项,每次渲染后都执行
useEffect(() => {
setCount(count + 1); // 改变了 state,触发重新渲染
});
return <div>{count}</div>;
}
执行流程:
- 组件首次渲染,
count为 0,副作用运行 →setCount(1)。 count变为 1,触发第二次渲染。- 第二次渲染后,副作用再次运行 →
setCount(2)。 - 这个过程无限重复,直到浏览器卡死或报错。
4. 与“传空数组”的关键区别
很多开发者误以为“不传参数”和“传空数组”效果类似,实则完全不同:
- 空数组
[]:告诉 React 这个副作用不依赖于任何 props 或 state,所以只会执行一次(挂载时),相当于componentDidMount。 - 不传第二个参数:意味着只要组件渲染,副作用就必须执行。React 完全不跳过这个效果,每次都会重新运行它。
5. 什么时候会故意不传第二个参数?
通常情况下,不传依赖数组是一个危险的模式,应该极力避免。但在极少数场景下,你可能需要这种“每次渲染都执行”的行为:
- 记录渲染次数或日志:用于调试或监控,尽管现在更推荐使用
useRef配合useEffect。 - 动态属性更新:你在副作用中需要读取最新的 DOM 属性(如滚动位置),但这些属性并不由 React 控制,所以每次渲染后都需要重新读取。
不过,现代 React 已经提供了如 useLayoutEffect 或直接在事件处理中处理的更好方案,所以故意不传依赖项的例子非常罕见。
6. 使用 ESLint 规则保护自己
React 官方强烈推荐配置 eslint-plugin-react-hooks,其中的 exhaustive-deps 规则会检查 useEffect 的依赖项是否完整。
- 如果你不传第二个参数,但回调中使用了任何外部变量(state、props),ESLint 会发出警告,提醒你可能需要添加依赖数组。
- 如果你确实需要每次渲染都执行,且确认不会造成问题,可以临时禁用该行规则,但务必添加注释解释原因。
useEffect(() => {
// 确实需要每次渲染后执行,且不会在这里更新 state
// eslint-disable-next-line react-hooks/exhaustive-deps
analytics.logRender();
});
7. 实际情况演示:对比实验
假设有一个显示当前时间的组件:
function CurrentTime() {
const [time, setTime] = useState(new Date().toLocaleTimeString());
// ❌ 不传第二个参数:每次渲染后都会重新设置定时器
// 导致定时器重复创建,且时间疯狂刷新
useEffect(() => {
const timer = setInterval(() => {
setTime(new Date().toLocaleTimeString());
}, 1000);
// 没有清理函数也会导致内存泄漏
});
return <div>{time}</div>;
}
修正的方式是传入空数组 [],让定时器只在挂载时创建,并返回清理函数在卸载时销毁:
useEffect(() => {
const timer = setInterval(() => {
setTime(new Date().toLocaleTimeString());
}, 1000);
return () => clearInterval(timer);
}, []); // 👈 关键:传入空数组
8. 总结
- 不传第二个参数 ≈ 每次渲染后无条件执行。
- 这很容易导致无限循环,尤其在副作用中更新 state 时。
- 绝不要把它当作“默认模式”——如果你不确定依赖项是什么,至少应该传入空数组
[],或者明确列出所有依赖。 - 使用 ESLint 规则辅助检查,保持代码的可预测性和性能。
记住:useEffect 的第二个参数不是可选项,而是一种控制机制。省略它,就是将控制权完全交给 React 的每次渲染,代价往往是你不想要的。