useEffect的返回函数会在组件挂载时执行吗?如何仅在卸载时运行?
现象说明
你遇到的表现是完全正常的,不是useEffect的执行逻辑不符合预期,根源是React 18及以上版本默认开启的**严格模式(Strict Mode)**在开发环境的特殊检测机制:
- 开发环境下,严格模式会对所有组件做一次「挂载→卸载→重新挂载」的模拟流程,用来提前排查副作用未正确清理导致的内存泄漏、重复订阅等问题。你首次访问PageOne时看到的
PageOne打印,就是第一次模拟卸载时触发清理函数打出来的,不是组件真的被路由卸载了。 - 生产环境打包后不会存在这个双重调用的逻辑,你当前写的空依赖
useEffect,其返回的清理函数本身就只会在组件真实卸载的时候执行。 - 从PageOne跳转到PageTwo时打印的第一条
PageOne是PageOne真实路由卸载触发的,后面的PageTwo和你首次进PageOne的打印同理,是严格模式模拟PageTwo卸载打出来的。
实现清理逻辑仅在真实卸载时执行的方法
如果你的目标是让清理逻辑只在组件真正被卸载(比如路由切走、组件被条件渲染移除)时执行,分两种场景处理:
- 生产环境下你当前的代码已经满足要求,不需要做任何修改。
- 如果是开发环境下不想被严格模式的模拟卸载触发逻辑,有两种可选方案:
方案1:移除根节点的严格模式包裹
找到项目入口文件(一般是main.jsx/main.tsx),删掉渲染根组件时的<React.StrictMode>包裹即可:// 修改前 ReactDOM.createRoot(document.getElementById('root')).render( <React.StrictMode> <App /> </React.StrictMode> ) // 修改后 ReactDOM.createRoot(document.getElementById('root')).render( <App /> )不推荐长期关闭严格模式,它能在开发阶段帮你提前暴露很多隐蔽的副作用问题,减少线上bug。
方案2:用ref标记组件真实挂载状态,过滤掉模拟卸载的触发
不需要关闭严格模式,通过ref判断是否是真实卸载即可,示例代码:import { useEffect, useRef } from "react"; import { Link } from "react-router-dom"; export const PageOne = () => { const mountedStatus = useRef(false); useEffect(() => { mountedStatus.current = true; return () => { // 仅当组件真实完成挂载后触发的卸载,才执行清理逻辑 if (mountedStatus.current) { console.log("PageOne 真实卸载"); // 此处写你需要在卸载时执行的清理逻辑:比如取消请求、清除定时器、解绑事件等 mountedStatus.current = false; } }; }, []); return ( <> <h1>PageOne</h1> <Link to="/two">Page two</Link> </> ); }
内容的提问来源于stack exchange,提问作者Miguel Angel GEQ
相关产品推荐
相关产品推荐

