useEffect钩子中自定义对象重复初始化且无法销毁的问题排查
问题排查:React中原生JS组件重复初始化且销毁失败
问题场景
- 原生JS组件
TestComponent:构造函数创建每秒触发的定时器,destroy方法负责清除定时器,要求在DOM挂载时初始化、移除时销毁 - React组件
TestApp通过useState存储组件实例,在useEffect中初始化实例并更新状态,同时在清理函数调用destroy - 出现问题:组件初始化两次(每秒打印两次"interval is ticking"),且
destroy从未执行,清理阶段testComponent状态为null
原因分析
重复初始化的原因
React 18+的开发环境默认开启Strict Mode(严格模式),这个模式会刻意重复执行useEffect的初始化逻辑两次,目的是检测组件中是否存在副作用泄漏问题。这就是你看到两次定时器打印的直接原因,生产环境下Strict Mode不会触发该重复执行逻辑。destroy无法执行的原因
useEffect的清理函数是通过闭包捕获状态值的。第一次执行useEffect时,testComponent还是初始的null,此时注册的清理函数引用的就是这个null;当调用setTestComponent更新状态后,闭包已改变,但旧的清理函数不会同步更新,因此组件卸载或依赖变化触发清理时,本质是调用null.destroy(),自然不会执行销毁逻辑。
修复方案
1. 解决开发环境重复初始化
如果只是开发环境的问题,生产环境不会出现,可直接忽略;若想在开发环境避免该情况,删除根组件外层的<StrictMode>标签即可。
2. 修复destroy不执行的问题
用useRef替代useState存储组件实例,useRef的current属性是可变的,不受闭包限制,清理函数能直接获取最新实例:
import { useEffect, useRef } from 'react'; function TestApp() { const testComponentRef = useRef(null); useEffect(() => { // 初始化原生组件实例 testComponentRef.current = new TestComponent(); // 清理函数:销毁实例 return () => { testComponentRef.current?.destroy(); }; }, []); // 空依赖数组确保只执行一次(生产环境) return <div>Test App</div>; }
即便在Strict Mode下,React会先执行第一次初始化的清理函数销毁第一个实例,再执行第二次初始化创建新实例,不会出现定时器泄漏问题。
内容的提问来源于stack exchange,提问作者Marvin3
相关产品推荐
相关产品推荐

