如何让已挂起组件卸载时执行useEffect的清理函数?
解决组件挂起后卸载时useEffect清理函数不执行的问题
当组件抛出Suspense异常进入挂起状态时,它还未完成commit阶段,useEffect的回调函数根本没有机会执行——而清理函数只有在effect被执行过的前提下,才会在组件卸载或依赖更新时运行,这就是导致内存泄漏的核心原因。
以下是几种可行的解决方案:
方案1:触发挂起前手动执行清理
通过useRef存储清理函数,在触发挂起的操作中先手动调用清理逻辑,再切换挂起状态:
function Foo() { const [isSuspended, setIsSuspended] = useState(false); const cleanupRef = useRef(null); useEffect(() => { const eventHandler = () => { // 你的事件处理逻辑 }; window.addEventListener('resize', eventHandler); // 将清理函数存入ref cleanupRef.current = () => { window.removeEventListener('resize', eventHandler); }; return cleanupRef.current; }, []); const handleTriggerSuspend = () => { // 先手动执行清理 if (cleanupRef.current) { cleanupRef.current(); cleanupRef.current = null; } // 再触发挂起 setIsSuspended(true); }; if (isSuspended) { throw new Promise(resolve => setTimeout(resolve, 1500)); // 模拟Suspense异步操作 } return <button onClick={handleTriggerSuspend}>触发挂起</button>; }
方案2:用状态控制effect的激活状态
通过额外的状态变量控制effect是否执行,在触发挂起前先关闭effect,等待清理完成后再切换挂起状态:
function Foo() { const [isSuspended, setIsSuspended] = useState(false); const [isEffectEnabled, setIsEffectEnabled] = useState(true); useEffect(() => { if (!isEffectEnabled) return; const eventHandler = () => { // 你的事件处理逻辑 }; window.addEventListener('resize', eventHandler); return () => { window.removeEventListener('resize', eventHandler); }; }, [isEffectEnabled]); const handleTriggerSuspend = () => { // 先禁用effect,触发清理 setIsEffectEnabled(false); // 延迟触发挂起,确保清理逻辑执行完成 setTimeout(() => setIsSuspended(true), 0); }; if (isSuspended) { throw new Promise(resolve => setTimeout(resolve, 1500)); } return <button onClick={handleTriggerSuspend}>触发挂起</button>; }
方案3:重构为标准Suspense资源加载
如果挂起逻辑基于数据加载,建议将异步资源封装为React Suspense兼容的资源(比如自定义资源管理器),而非在组件内部直接抛出异常。这种模式下,组件只有在资源加载完成后才会渲染并执行effect;若组件在资源加载完成前卸载,由于effect从未执行过,也就不存在需要清理的资源,从根源避免泄漏问题。
内容的提问来源于stack exchange,提问作者Leo Jiang
相关产品推荐
相关产品推荐

