React 18升级切换组件时出现removeEventListener空值读取错误
React 16.12 升级到 18 后组件切换出现 removeEventListener 空值错误排查
问题概述
将React代码库从v16.12升级到v18后,切换组件时触发以下错误:
Uncaught TypeError: Cannot read properties of null (reading 'removeEventListener') at eval (index.js:381:295102) at safelyCallDestroy (react-dom.development.js:22927:5) at commitHookEffectListUnmount (react-dom.development.js:23095:11) at commitPassiveUnmountInsideDeletedTreeOnFiber (react-dom.development.js:25093:11) at commitPassiveUnmountEffectsInsideOfDeletedTree_begin (react-dom.development.js:25043:5) at commitPassiveUnmountEffects_begin (react-dom.development.js:24951:11) at commitPassiveUnmountEffects (react-dom.development.js:24936:3) at flushPassiveEffectsImpl (react-dom.development.js:27033:3) at flushPassiveEffects (react-dom.development.js:26979:14) at commitRootImpl (react-dom.development.js:26930:5)
错误指向打包后index.js第381行的useEffect清理函数:
Object(De.useEffect)(function() { if (H(), null !== E.current) return E.current.addEventListener("scroll", t), window.addEventListener("scroll", n), window.addEventListener("resize", t), window.addEventListener("resize", n), window.addEventListener("resize", H), function() { E.current.removeEventListener("scroll", t), window.removeEventListener("scroll", n), window.removeEventListener("resize", t), window.removeEventListener("resize", n), window.removeEventListener("resize", H) } }, []),
已尝试操作
- 在清理函数中添加空值判断,无效
- 注释所有
removeEventListener代码,错误依旧出现 - 升级webpack和webpack-cli到最新版本,编译显示成功但问题未解决:
./src/components/Audiences/Filters/Index.js 9.46 KiB [built] [code generated] webpack 5.88.2 compiled successfully in 1175 ms
解决方向与排查建议
1. 确认代码修改是否真的被编译生效
- 强制刷新浏览器缓存:使用
Ctrl+Shift+R(Windows/Linux)或Cmd+Shift+R(Mac)强制刷新,避免浏览器加载旧的打包文件 - 检查打包后的代码:直接查看
dist/index.js对应行的内容,确认你的修改(比如注释清理函数)是否被编译进去。如果没生效,排查:- 确认修改的文件路径是否正确,是否是webpack打包的入口文件依赖的组件
- 检查webpack配置中的
cache选项,若开启了持久化缓存,删除node_modules/.cache或webpack指定的缓存目录后重新打包 - 检查webpack devServer的
watch配置,确保监听了src目录下的文件变化
2. 适配React 18的useEffect行为变化
React 18在开发环境严格模式下会模拟组件的挂载-卸载-重新挂载流程,双重调用useEffect的挂载和清理函数,可能导致清理时ref.current已变为null。正确的处理方式:
- 在清理函数中也添加空值判断:
useEffect(() => { if (filtersContainerRef.current !== null) { filtersContainerRef.current.addEventListener('scroll', removeActiveFilter); window.addEventListener('scroll', setFiltersContainerOffsetTop); window.addEventListener('resize', removeActiveFilter); window.addEventListener('resize', setFiltersContainerOffsetTop); } return () => { // 清理DOM元素监听前先判断ref是否存在 if (filtersContainerRef.current) { filtersContainerRef.current.removeEventListener('scroll', removeActiveFilter); } // window监听无需判断,直接移除 window.removeEventListener('scroll', setFiltersContainerOffsetTop); window.removeEventListener('resize', removeActiveFilter); window.removeEventListener('resize', setFiltersContainerOffsetTop); }; }, []); - 确保监听的回调函数是稳定的:如果
removeActiveFilter或setFiltersContainerOffsetTop是组件内定义的函数,用useCallback包裹,避免每次渲染创建新函数导致清理时无法匹配到正确的监听函数:const removeActiveFilter = useCallback(() => { // 函数逻辑 }, []); const setFiltersContainerOffsetTop = useCallback(() => { // 函数逻辑 }, []);
3. 排查依赖冲突
- 检查项目中是否存在多个版本的React/ReactDOM:执行
npm ls react react-dom,如果有多个版本,清理旧版本后重新安装 - 检查第三方组件库是否支持React 18:如果错误代码来自第三方组件,升级该组件到支持React 18的版本
4. 其他排查手段
- 开启React严格模式:在根组件包裹
<StrictMode>,查看控制台是否有其他警告信息,帮助定位潜在问题 - 使用React DevTools:查看组件的挂载/卸载流程,确认清理函数执行时的组件状态和ref值
内容的提问来源于stack exchange,提问作者jaylamping
相关产品推荐
相关产品推荐

