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

React useEffect多次触发:按钮点击计数器前3次正常后续运行异常

问题原因分析
  • 事件监听器重复绑定:你的useEffect依赖项为power,每次power状态变化时都会重新执行useEffect逻辑,给按钮新增一个点击事件监听,且没有清理之前绑定的监听。点击几次后就会存在多个监听回调,单次点击会多次触发changePower,导致计数器和状态切换异常。
  • 状态闭包陷阱:回调函数里读取的count是当前useEffect闭包缓存的旧值,不是最新的状态值,多次回调触发时计数更新会出现覆盖问题。
  • 不符合React状态驱动UI的设计逻辑:手动操作DOM修改背景色、手动绑定DOM事件属于原生JS实现思路,在React中可以通过声明式写法实现,从根源规避这类异常。
修正方案

推荐写法(符合React开发规范)

直接用React内置的onClick绑定事件、用状态绑定样式,不需要手动操作DOM:

import { useState } from 'react'

function YourComponent() {
  // 初始化状态:默认off状态,计数器从0开始
  const [power, setPower] = useState('off')
  const [count, setCount] = useState(0)

  const changePower = () => {
    // 状态更新用函数式写法,确保取到上一轮的最新值
    setPower(prevPower => prevPower === 'on' ? 'off' : 'on')
    setCount(prevCount => prevCount + 1)
  }

  return (
    <button 
      id="btn-trigger" 
      style={{ backgroundColor: power === 'on' ? 'lime' : 'red' }}
      onClick={changePower}
    >
      点击切换
    </button>
  )
}

兼容手动绑定事件的修正方案

如果确实有特殊场景必须手动绑定事件监听,需要补充useEffect清理逻辑,同时修改状态更新写法:

useEffect(() => {
  const btn = document.getElementById('btn-trigger')
  const changePower = () => {
    if (power === 'on') {
      btn.style.backgroundColor = "red";
      setPower('off');
    } else if (power==='off') {
      btn.style.backgroundColor = "lime";
      setPower('on');
    }
    // 函数式写法读取上一轮最新的count值
    setCount(prevCount => prevCount + 1);
  }
  btn.addEventListener('click', changePower);
  console.log(count);
  // 新增清理函数,副作用卸载时移除事件监听,避免重复绑定
  return () => {
    btn.removeEventListener('click', changePower)
  }
}, [power])

内容的提问来源于stack exchange,提问作者Fransedo Ravelino

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 22:15:02