React接收多个同时事件后的重渲染行为是否有明确定义?
React合成事件与原生事件的状态更新批处理差异
场景1:React合成事件绑定
点击按钮后,span和button的合成事件处理函数都会执行,但React会将两次setState批处理,最终仅重渲染一次:
const {useRef, useState} = React; function App() { const [_, setState] = useState(null); const renderCount = useRef(0); renderCount.current += 1; console.log("rendering"); return ( <div> {`Render count: ${renderCount.current} `} <span onClick={() => { console.log("span clicked"); setState({}); }}> <button onClick={() => { console.log("button clicked"); setState({}); }}>Send click events</button> </span> </div> ); } ReactDOM.createRoot( document.getElementById("root") ).render( <App /> );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script> <div id="root"></div>
场景2:原生事件绑定
通过addEventListener绑定事件后,每次setState都会触发一次重渲染,点击按钮后会重渲染两次:
const {useRef, useState, useEffect} = React; function App() { const [_, setState] = useState(null); const spanRef = useRef(); const btnRef = useRef(); const renderCount = useRef(0); renderCount.current += 1; console.log("rendering"); useEffect(() => { const handler1 = () => { console.log("span clicked"); setState({}); }; const handler2 = () => { console.log("button clicked"); setState({}); }; const span = spanRef.current; span.addEventListener("click", handler1); const btn = btnRef.current; btn.addEventListener("click", handler2); return () => { span.removeEventListener("click", handler1); btn.removeEventListener("click", handler2); }; }, []); return ( <div> {`Render count: ${renderCount.current} `} <span ref={spanRef}> <button ref={btnRef}>Send click events</button> </span> </div> ); } ReactDOM.createRoot( document.getElementById("root") ).render( <App /> );
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script> <div id="root"></div>
行为解析
这种差异的核心在于React的自动批处理机制仅在受控上下文内生效:
- React合成事件、组件生命周期钩子、
useEffect内部的状态更新,都会被自动批处理——React会收集同一事件流中的所有setState调用,等到事件处理流程结束后再统一触发重渲染,避免不必要的重复渲染。 - 原生事件处理函数、定时器回调、Promise回调等非React受控的上下文中,
setState会触发同步重渲染,无法享受自动批处理。这是因为React无法追踪这些上下文的执行时机,无法将多个更新合并。
关于useEffect的执行时机:即使原生事件触发了两次重渲染,useEffect也不会在两次重渲染之间执行。React会等到所有pending的更新完成(也就是两次重渲染都结束),并且浏览器完成UI绘制后,再统一执行所有useEffect回调。所以两次重渲染对应的useEffect会被合并,只执行一次清理和一次新的effect。
内容的提问来源于stack exchange,提问作者Dennis Kats
相关产品推荐
相关产品推荐

