引言 💭
在 React 中,useEffect
的清理函数(cleanup)是副作用管理的关键机制。首次挂载时它不会执行,而是在依赖更新或组件卸载时才触发,遵循一套严格的执行顺序。
1. Cleanup 函数的本质
useEffect
的 return
清理函数用于清除上一次渲染产生的副作用,而不是当前渲染的副作用。
- 首次渲染:没有上一次副作用,因此不执行 cleanup。
- 更新或卸载:React 会先执行 cleanup,再运行新的副作用逻辑。
2. 核心执行机制
① 首次渲染(挂载)
- 执行 effect 函数
- 不执行 cleanup(因为没有旧副作用)
② 依赖项更新
执行顺序:
- 执行上一次的 cleanup(清理旧副作用)
- 执行新的 effect(注册新副作用)
③ 组件卸载
- 仅执行 cleanup,用于释放最后的副作用
3. 示例代码
javascript
function Example({ prop }) {
useEffect(() => {
console.log("Effect 执行");
return () => {
console.log("Cleanup 执行");
};
}, [prop]);
return <div>Example</div>;
}
4. 与类组件对比
useEffect
的清理机制相当于类组件中两个生命周期方法的组合:
- 首次挂载 :类似
componentDidMount
(只执行 effect)。 - 更新/卸载 :先
componentWillUnmount
(清理),再componentDidUpdate
(运行新 effect)。
5. 为什么要这样设计?
- 避免无意义清理:首次渲染时没有旧副作用。
- 保持逻辑一致:始终清理"上一次"的副作用。
- 性能优化:减少额外的函数调用。
6. 运行验证
javascript
import { useEffect, useState } from "react";
function Demo() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Effect 运行,count =", count);
return () => {
console.log("Cleanup 运行,count =", count);
};
}, [count]);
return <button onClick={() => setCount(count + 1)}>点击 {count}</button>;
}
输出顺序:
-
首次渲染
iniEffect 运行,count = 0
-
点击按钮(count → 1)
iniCleanup 运行,count = 0 Effect 运行,count = 1
-
卸载组件
iniCleanup 运行,count = 1
7. 特殊情况:严格模式(Strict Mode)
在 开发模式 下,React 会故意执行一次"挂载 → 卸载 → 重新挂载",以检测副作用是否安全:
arduino
Effect 运行 // 挂载
Cleanup 运行 // 卸载
Effect 运行 // 重新挂载
⚠️ 这只是开发阶段的调试行为,生产环境不会发生。
8. 关键特性
-
cleanup 永远对应上一次 effect
-
执行时机在浏览器绘制后(异步)
-
多个 effect 的执行顺序:
- effect:按定义顺序执行
- cleanup:按相反顺序执行
javascript
useEffect(() => { /* effect 1 */ return () => { /* cleanup 1 */ } });
useEffect(() => { /* effect 2 */ return () => { /* cleanup 2 */ } });
// 执行顺序:effect1 → effect2 → cleanup2 → cleanup1
总结✒️
场景 | 是否执行 cleanup | 原因 |
---|---|---|
首次挂载 | ❌ 否 | 没有旧副作用 |
依赖项变化 | ✅ 是 | 清理旧副作用,再注册新副作用 |
组件卸载 | ✅ 是 | 清理最后一个副作用 |
严格模式(开发) | ✅ 可能两次 | React 故意测试卸载逻辑 |
return
清理函数始终是滞后执行的 :它清理的是上一次的副作用,而不是当前的。
