You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 01:36:19