useEffect
的清理函数是 React 中管理副作用生命周期的关键机制,其执行时机遵循以下规则:
1. 核心执行时机
- 组件卸载时:清理函数在组件从 DOM 中移除前执行,防止内存泄漏(如取消订阅、清除定时器)。
- 副作用重复执行前 :如果
useEffect
因依赖项变化而重新执行,上一次 副作用的清理函数会在新副作用执行前触发。
2. 详细执行流程
首次渲染
- 组件渲染完成
- 执行
useEffect
中的副作用函数(不执行清理函数)
后续更新(依赖项变化时)
- 组件重新渲染
- 执行上一次副作用的清理函数
- 执行新的副作用函数
组件卸载
- 执行最后一次副作用的清理函数
3. 示例说明
jsx
const Example = ({ value }) => {
useEffect(() => {
const subscription = api.subscribe(value);
return () => {
subscription.unsubscribe(); // 清理函数
};
}, [value]); // 依赖项:value 变化时重新执行
return <div>Listening to {value}</div>;
};
-
首次渲染 :
value = "A"
- 组件渲染
<div>Listening to A</div>
- 执行
subscribe("A")
- 组件渲染
-
更新后 :
value = "B"
- 组件重新渲染
<div>Listening to B</div>
- 执行上一次的清理函数:
unsubscribe("A")
- 执行新的副作用:
subscribe("B")
- 组件重新渲染
-
组件卸载:
- 执行最后一次清理函数:
unsubscribe("B")
- 执行最后一次清理函数:
4. 特殊场景说明
依赖项为空数组 []
清理函数仅在组件卸载时执行(相当于 componentWillUnmount
):
jsx
useEffect(() => {
const timer = setInterval(() => console.log("Tick"), 1000);
return () => clearInterval(timer); // 仅卸载时执行
}, []);
无依赖项数组
每次渲染都会执行副作用和清理函数(慎用,可能导致性能问题):
jsx
useEffect(() => {
// 每次渲染都执行
return () => {
// 每次重新渲染前都清理
};
});
5. 注意事项
-
异步清理:清理函数必须是同步的(不能返回 Promise)。若需异步操作,可在清理函数中触发状态更新:
jsxuseEffect(() => { let isMounted = true; fetchData().then(data => { if (isMounted) setData(data); }); return () => (isMounted = false); // 同步标记组件已卸载 }, []);
-
严格模式(开发环境) :React 18 在开发环境会额外调用一次清理函数和副作用,用于检测潜在问题,但生产环境不会。
总结
清理函数的核心作用是确保副作用的影响被正确撤销。通过在组件卸载前和副作用重复执行前执行清理,React 保证了资源的正确释放和状态的一致性。理解这一点对于避免内存泄漏和竞态条件至关重要。