← Назад к вопросам

Как устроен батчинг в React v17.0?

2.0 Middle🔥 211 комментариев
#React#Архитектура и паттерны

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Механизм батчинга (Batching) в React 17.0

Батчинг (объединение в пакеты) — это ключевая оптимизация в React, которая позволяет группировать несколько обновлений состояния в один повторный рендер для повышения производительности. В React 17.0 этот механизм претерпел существенные изменения по сравнению с предыдущими версиями, особенно в контексте асинхронных событий.

Как работал батчинг до React 17

До версии 17 React автоматически объединял обновления только внутри React-событий (например, onClick, onChange). Это означало, что если вы вызывали несколько setState внутри обработчика события React, они батчились. Однако асинхронные операции (например, setTimeout, Promise, нативные обработчики событий) не батчились, что могло приводить к лишним рендерам.

// ДО React 17: внутри React-событий — батчинг
handleClick = () => {
  setCount(1);     // Эти обновления объединяются
  setFlag(true);   // в один рендер
};

// ДО React 17: вне React-событий — НЕТ батчинга
setTimeout(() => {
  setCount(1);     // Каждое вызовет отдельный
  setFlag(true);   // ре-рендер (два рендера)
}, 1000);

Изменения в React 17.0

В React 17 батчинг был расширен на большинство асинхронных сценариев по умолчанию. Теперь обновления автоматически батчатся внутри:

  • Все React-события (как и раньше).
  • Асинхронные функции (например, внутри setTimeout, Promise, async/await).
  • Нативные обработчики событий, если они обернуты в ReactDOM.unstable_batchedUpdates.

Основная причина: React 17 изменяет способ прикрепления обработчиков событий к корню приложения, используя новую систему "делегирования событий к корню" (root event delegation). Это позволяет React контролировать выполнение обновлений более эффективно.

Практический пример в React 17

function Component() {
  const [count, setCount] = useState(0);
  const [flag, setFlag] = useState(false);

  const handleAsync = async () => {
    // В React 17 эти обновления БУДУТ объединены
    setCount(c => c + 1);
    setFlag(f => !f);
    // Только один ре-рендер произойдет
  };

  useEffect(() => {
    setTimeout(() => {
      // В React 17 эти также батчатся
      setCount(10);
      setFlag(true);
    }, 1000);
  }, []);

  return <button onClick={handleAsync}>Click</button>;
}

Как это реализовано под капотом

React использует внутреннюю систему "исполнителя транзакций" (transaction executor) для очереди обновлений:

  1. При вызове setState обновление помещается в очередь (updateQueue).
  2. React проверяет, выполняется ли уже "батчинговая транзакция" (через флаг executionContext).
  3. Если да — обновление добавляется в текущую очередь. Если нет — создается новая транзакция.
  4. По завершению (например, после выполнения всего обработчика) React применяет все накопленные обновления одним рендером.

Исключения и контроль

  • Экстренные случаи: В редких случаях, когда требуется немедленное обновление DOM (например, измерение элементов), можно использовать flushSync из React 18, но в 17.0 для этого использовались обходные пути.
  • Ручной контроль: В React 17 все еще доступен ReactDOM.unstable_batchedUpdates для принудительного батчинга в нестандартных сценариях.

Преимущества батчинга в React 17

  • Улучшенная производительность: Меньше ре-рендеров → меньше работы для браузера.
  • Согласованность поведения: Единый подход к батчингу внутри и вне событий.
  • Упрощение кода: Разработчикам реже нужно вручную оптимизировать последовательные обновления.

Важно отметить, что в React 18 батчинг был улучшен еще дальше с введением "автоматического батчинга" для всех обновлений (даже вне обработчиков событий) благодаря новой конкурентной архитектуре. Однако React 17 заложил критический фундамент, расширив зону покрытия батчинга на большинство асинхронных операций, что сделало поведение приложений более предсказуемым и эффективным.