React原始状态未改变时为何会触发重新渲染?
为什么调用setCounter(相同值)仍会触发重新渲染?
先看你提供的React代码:
function App(props) { const [counter, setCounter] = useState(0); useEffect(() => { document.addEventListener('click', () => { setCounter(counter+1); }); }, []); console.log('render', counter); return ( <div className="App"> <h1>{counter}</h1> </div> ); }
你已经发现useEffect的依赖问题:因为依赖项是空数组,事件处理函数捕获的是初始的counter=0,所以第一次点击会把counter设为1,第二次点击还是会调用setCounter(0+1)=1——而此时counter已经是1,但依然触发了渲染,控制台输出render 1。
核心原因
React的默认行为是:只要调用了状态更新函数(比如setCounter),无论新值和当前状态是否相同,都会触发组件重新渲染。
React并不会自动对新旧状态值做浅比较来跳过渲染,这个步骤需要你手动通过优化手段实现:
- 函数组件可以用
React.memo包裹组件,它会对props做浅比较,若props未变化则跳过渲染; - 类组件可以使用
React.PureComponent或者自定义shouldComponentUpdate方法来控制渲染时机。
不过要注意:即使触发了这次渲染,最终DOM并不会有任何更新——因为React会在渲染后对比虚拟DOM,发现内容没有变化,就会跳过真实DOM的更新操作,所以用户看不到界面变化,但组件函数本身还是会执行,控制台的console.log会打印出来。
顺便修复原bug
回到你的代码,要解决事件处理函数捕获过时状态的问题,有两种方案:
- 把
counter加入useEffect的依赖数组,这样每次counter变化时都会重新绑定事件处理函数:
useEffect(() => { const handleClick = () => { setCounter(counter + 1); }; document.addEventListener('click', handleClick); return () => document.removeEventListener('click', handleClick); // 别忘了清理事件监听 }, [counter]);
- 使用函数式更新,直接获取最新的状态值,无需依赖
counter:
useEffect(() => { document.addEventListener('click', () => { setCounter(prevCounter => prevCounter + 1); }); }, []);
第二种方案更优,因为不需要频繁绑定/解绑事件,而且总能拿到最新的状态值。
内容的提问来源于stack exchange,提问作者Mystical
相关产品推荐
相关产品推荐

