React(Next.js)组件点击按钮时toggle函数触发两次如何解决
问题根因
点击一次触发两次的核心原因有两个:
- Next.js开发环境默认开启React严格模式(Strict Mode),为了检测未正确清理的副作用,开发环境下组件首次挂载时会主动执行两次「挂载→卸载→再挂载」的流程。你的useEffect没有编写清理逻辑,第一次执行时绑定的点击事件没有被移除,第二次执行时又给同一个按钮绑定了一次新的相同监听,等于按钮上叠了两个click回调,点击一次自然触发两次。
- 你当前的写法本身就有隐患:直接在useEffect里用原生DOM API查询元素、绑定事件,既不符合React的开发范式,也没有做事件解绑,后续只要组件重渲染触发useEffect重新执行,就会不断叠加新的事件监听器,哪怕关了严格模式,用久了也会出现点击一次触发N次的问题。
修复方法
优先用React标准的事件绑定写法,完全不需要在useEffect里操作DOM找元素:
export default function MainComponent() { const toggleMode = () => { const root = document.documentElement; root.classList.toggle("dark"); console.log("click"); }; return ( <> {/* 其他页面内容 */} <button id="toggle-btn" onClick={toggleMode}>切换主题</button> </> ) }
如果有特殊需求必须在useEffect里绑定原生事件,一定要给useEffect传入空依赖数组保证只执行一次,同时返回清理函数解绑旧事件,避免重复绑定:
useEffect(() => { const toggleMode = () => { const root = document.documentElement; root.classList.toggle("dark"); console.log("click"); }; const toggleBtn = document.querySelector("#toggle-btn"); // 提前判断元素存在,避免控制台报错 if (toggleBtn) { toggleBtn.addEventListener("click", toggleMode); } // 清理函数:effect重新执行/组件卸载前自动调用,移除已绑定的事件 return () => { if (toggleBtn) { toggleBtn.removeEventListener("click", toggleMode); } }; }, []); // 空依赖数组,仅组件首次挂载时执行绑定逻辑
补充:严格模式下的重复执行逻辑仅在开发环境生效,生产环境构建后不会触发,但缺失事件清理的问题在生产环境依然存在,必须按上述方式补全清理逻辑,避免内存泄漏和重复触发问题。
内容的提问来源于stack exchange,提问作者Traznix
相关产品推荐
相关产品推荐

