文章目录

React18的自动批处理与flushSync的使用场景

发布于 2026-05-14 06:16:35 · 浏览 9 次 · 评论 0 条

React18的自动批处理与flushSync的使用场景

React 18 引入了自动批处理机制,这是对性能优化的一次重大升级。同时,为了应对特殊场景,新增了 flushSync API 允许开发者手动退出批处理。


理解批处理的核心逻辑

批处理是指 React 将多个状态更新合并到一次重新渲染中,以提高性能。

在 React 18 之前,批处理仅在浏览器事件(如点击)中生效。如果在 Promise、setTimeout 或原生 DOM 事件中更新状态,React 不会进行批处理,导致组件渲染多次。

React 18 的自动批处理打破了这一限制,无论更新发生在何处,React 都会自动合并更新。


自动批处理的实际表现

通过代码对比可以直观理解 React 18 的变化。

场景一:Promise 或 setTimeout 内部更新

假设有一个组件,点击按钮后在 setTimeout 中连续更新两个状态。

  1. 创建 一个函数组件,包含两个独立的状态 countflag

    import { useState } from 'react';
    
    function MyComponent() {
      const [count, setCount] = useState(0);
      const [flag, setFlag] = useState(false);
    
      function handleClick() {
        setTimeout(() => {
          // React 17: 渲染两次 (count变,flag变)
          // React 18: 渲染一次 (自动批处理)
          setCount(c => c + 1);
          setFlag(f => !f);
        }, 0);
      }
    
      return (
        <button onClick={handleClick}>
          Count: {count}, Flag: {String(flag)}
        </button>
      );
    }
  2. 观察 渲染行为。

    • 在 React 17 中,setCountsetFlag 各触发一次渲染。
    • 在 React 18 中,React 检测到这两次更新发生在同一个事件循环上下文中,自动合并 为一次渲染。

场景二:跨组件更新

当多个组件的状态在同一个回调中更新时,自动批处理同样生效。

  1. 定义 两个子组件和一个父组件。

    function ChildA({ onClick }) {
      return <button onClick={onClick}>Click Me</button>;
    }
    
    function ChildB({ count }) {
      return <p>Count: {count}</p>;
    }
    
    export default function App() {
      const [count, setCount] = useState(0);
      const [text, setText] = useState('');
    
      const handleClick = () => {
        fetch('/api/data').then(() => {
          // 异步回调中的自动批处理
          setCount(1);
          setText('Updated');
          // React 18 只会重新渲染一次
        });
      };
    
      return (
        <>
          <ChildA onClick={handleClick} />
          <ChildB count={count} />
        </>
      );
    }

flushSync 的使用场景

虽然自动批处理对性能有利,但在某些极端场景下,我们需要立即获取更新后的 DOM 状态,此时必须禁用批处理。flushSync 就是为了解决这个问题。

核心作用

flushSync 会强制 React 同步执行回调函数内的所有更新,并立即刷新 DOM,确保后续代码能够读取到最新的 DOM 状态。

使用步骤

  1. 导入 flushSyncreact-dom

    import { flushSync } from 'react-dom';
  2. 包裹 需要立即更新的状态逻辑。

    function handleChange() {
      // 强制同步更新
      flushSync(() => {
        setCount(count + 1);
      });
      // 代码执行到这里时,DOM 已经完成更新
      console.log(document.getElementById('my-div').textContent);
    }

典型案例:聚焦新输入框

假设点击按钮后新增一个输入框,并需要立即让该输入框获得焦点。如果没有 flushSync,React 可能会在事件处理结束后才批量渲染,导致 focus 代码执行时输入框还不存在。

  1. 编写 一个添加输入框的逻辑。

    import { useState, useRef } from 'react';
    import { flushSync } from 'react-dom';
    
    function FormList() {
      const [inputs, setInputs] = useState([{ id: 1 }]);
      const newInputRef = useRef(null);
    
      function handleAdd() {
        const newId = inputs.length + 1;
    
        // 错误做法(不使用 flushSync):
        // setInputs([...inputs, { id: newId }]);
        // newInputRef.current?.focus(); // 此时 DOM 尚未更新,current 为 null
    
        // 正确做法:
        flushSync(() => {
          setInputs([...inputs, { id: newId }]);
        });
    
        // 此时 DOM 已经包含新输入框
        // 注意:这里通常需要配合 ref 回调或 useEffect,但在 flushSync 后可直接操作 DOM
        document.getElementById(`input-${newId}`)?.focus();
          }
    
          return (
            <div>
              {inputs.map(input => (
                <input key={input.id} id={`input-${input.id}`} />
          ))}
          <button onClick={handleAdd}>Add Input</button>
        </div>
      );
    }

自动批处理与 flushSync 的决策流程

为了清晰判断何时使用默认行为,何时使用 flushSync,可以参考以下逻辑:

graph TD A["状态更新事件触发"] --> B{"是否需要立即读取更新后的 DOM?"} B -- "否 (大多数情况)" --> C["保持默认行为"] C --> D["React 18 自动批处理"] D --> E["性能最优: 减少渲染次数"] B -- "是 (如: 聚焦/滚动/计算布局)" --> F["使用 flushSync 包裹更新"] F --> G["强制同步渲染 DOM"] G --> H["执行后续 DOM 操作"]

关键注意事项

使用 flushSync 时必须极其谨慎,因为它会显著降低性能。

  1. 避免flushSync 回调外部进行不必要的逻辑包裹。
  2. 确认 只有在确实需要同步读取 DOM 更新结果时才使用。
  3. 警惕 flushSync 会刷新整个组件树的挂起状态,而不仅仅是包裹的组件。
特性 自动批处理 (默认) flushSync (强制同步)
更新方式 异步、合并 同步、立即
渲染次数 多次更新合并为 1 次 每次更新立即渲染
DOM 状态 更新后无法立即读取 更新后立即可用
性能影响 高 (优) 低 (可能卡顿)
适用场景 绝大多数业务逻辑 焦点管理、滚动定位、第三方库集成

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文