人人都会AI编程

React 18 自动批处理特性

更新时间:2026-07-11

什么是批处理

批处理(Batching)指的是 React 将多个状态更新合并为一次重新渲染,从而避免不必要的组件重复计算和 DOM 操作,提升性能。

在 React 18 之前,React 已经能够在合成事件和生命周期钩子中自动进行批处理,但对于原生事件、setTimeout、Promise、异步操作中的状态更新却无法合并,导致多次状态变更触发多次渲染。

React 17 的局限

以下代码在 React 17 中会触发两次渲染:

function handleClick() {
  // React 17 在合成事件中会批处理
  setCount(c => c + 1);
  setFlag(f => !f);
  // 两次更新合并为一次渲染 ✅
}

// 但在异步回调中,React 17 不会批处理
function handleAsync() {
  fetch('/api').then(() => {
    setCount(c => c + 1);  // 触发渲染
    setFlag(f => !f);       // 再次触发渲染 ❌
  });
}

这种不一致性会让开发者困惑,也可能导致意外的性能问题。

React 18 的自动批处理

React 18 通过引入 createRoot API 并重构更新机制,实现了所有更新都会自动批处理,无论它们发生在什么上下文:合成事件、原生事件、setTimeout、Promise、async/await 等异步操作。

// React 18 中,以下所有场景都会自动批处理
function App() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  // 场景1: 点击事件(合成事件)
  function handleClick() {
    setCount(c => c + 1);
    setFlag(f => !f);
    // 只触发一次渲染 ✅
  }

  // 场景2: Promise 回调
  function handleAsync() {
    fetch('/api').then(() => {
      setCount(c => c + 1);
      setFlag(f => !f);
      // 只触发一次渲染 ✅
    });
  }

  // 场景3: setTimeout 回调
  useEffect(() => {
    setTimeout(() => {
      setCount(c => c + 1);
      setFlag(f => !f);
      // 只触发一次渲染 ✅
    }, 1000);
  }, []);

  console.log('渲染'); // 在上述所有场景中都只会打印一次
  return <div>{count} - {flag.toString()}</div>;
}

自动批处理的原理

在 React 18 中,状态更新会被调度为一个微任务或通过内部的任务队列统一处理。当同一个事件循环内发生多个状态更新时,它们会被收集到一个更新队列,然后由 React 在合适的时机一次性处理,生成最新的状态并触发一次渲染。

这依赖于 React 新的调度系统(基于 Lane 模型的优先级调度),能够在任意上下文中将多个更新“包裹”在同一批次中。

如何退出批处理(执行同步更新)

在极少数场景下,你可能希望在一个状态更新后立即读取并基于新的 DOM 状态执行操作(例如手动聚焦输入框、测量元素尺寸),而批处理会延迟 DOM 更新。此时可以使用 ReactDOM.flushSync() 强制同步更新,但这会打断批处理,应谨慎使用。

import { flushSync } from 'react-dom';

function handleClick() {
  flushSync(() => {
    setCount(c => c + 1);
  });
  // 此时 DOM 已经更新,可以读取新的 DOM 状态
  flushSync(() => {
    setFlag(f => !f);
  });
  // 两次更新会触发两次同步渲染
}

对现有代码的影响

如果你从 React 17 升级到 React 18,并使用 createRoot 替换 ReactDOM.render,绝大多数时候你会立即享受自动批处理带来的性能提升,并且行为更加一致。只有在极少数依赖立即 DOM 更新的旧代码中,才需要用 flushSync 显式处理,但这种模式本身不推荐使用,应优先考虑用 useEffectuseLayoutEffect 来处理副作用。

开发中的注意事项

  • 不要依赖旧版的多次渲染行为:如果你在异步操作中手动计算渲染次数,升级后行为会改变。
  • 异步操作中的状态更新顺序仍然保持不变:批处理只影响渲染时机,不影响状态更新的执行顺序和合并逻辑。
  • 使用 React.StrictMode:在开发环境下,React 18 会双重调用某些函数(如 reducer、setState 更新函数)来帮助发现副作用问题,但这不影响生产环境的批处理行为。

总结:React 18 的自动批处理消除了困扰开发者多年的“同步与异步不一致”问题,让应用默认更高效,而你几乎不需要改动任何代码。