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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 05:54:58