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

Компоновались ли в один рендеринг несколько states вызванных подряд в React

2.0 Middle🔥 273 комментариев
#React#Оптимизация и производительность

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

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

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

Компоновались ли в один рендеринг несколько states вызванных подряд в React

Да, в современных версиях React (начиная с React 18 с включённым concurrent features или в React 17 и ниже в рамках одного синхронного события) несколько обновлений состояния (states), вызванных подряд в одном синхронном блоке кода, обычно "батчируются" (batch) в один ререндер. Это важная оптимизация для предотвращения избыточных повторных рендеров и повышения производительности.

Как работает батчинг (batching)

Батчинг — это процесс группировки нескольких обновлений состояния в один ререндер компонента. React "собирает" все вызовы setState (или функций обновления из useState/useReducer) в течение одного события (например, в обработчике клика) и применяет их вместе, выполняя ререндер только один раз.

Пример синхронного батчинга в обработчике события:

import { useState } from 'react';

function Component() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  const handleClick = () => {
    // Эти два обновления будут сбатчены в один ререндер
    setCount(count + 1);
    setText('Updated');
    // Несмотря на два вызова, компонент перерендерится только один раз
  };

  console.log('Render'); // Выведется один раз при клике

  return (
    <button onClick={handleClick}>
      Click me: {count}, {text}
    </button>
  );
}

Особенности в React 18 и автоматический батчинг

В React 18 с использованием createRoot (конкурентный режим) батчинг стал ещё более агрессивным — он работает не только в синхронных событиях, но и в асинхронном коде (например, в промисах, таймаутах, нативных обработчиках событий).

Пример батчинга в асинхронном коде в React 18:

function Component() {
  const [a, setA] = useState(0);
  const [b, setB] = useState(0);

  const handleAsyncClick = async () => {
    // В React 18 эти обновления также батчатся в один рендер
    setTimeout(() => {
      setA(1);
      setB(2);
    }, 100);
  };

  // В React 17 и ниже без батчинга в асинхронных операциях было бы два рендера
  // В React 18 — один рендер

  return <button onClick={handleAsyncClick}>Async update</button>;
}

Когда батчинг НЕ применяется

Однако есть случаи, когда батчинг не срабатывает, и каждое обновление состояния вызывает отдельный рендер:

  • В React 17 и ниже в асинхронном коде (например, внутри setTimeout, Promise.then, fetch.then) — каждое обновление вызывало отдельный ререндер.
  • При использовании flushSync из react-dom — этот API позволяет принудительно выключить батчинг для немедленного применения обновлений и синхронного ререндера.
    import { flushSync } from 'react-dom';
    
    flushSync(() => {
      setCount(1); // Рендер происходит сразу здесь
    });
    setText('Hi'); // Второй отдельный рендер
    

Ключевые выводы

  • Батчинг — это оптимизация по умолчанию в React для группировки обновлений состояния.
  • В React 18 батчинг работает повсеместно (включая асинхронный код) при использовании createRoot.
  • Цель батчинга — минимизировать количество ререндеров, что особенно важно для сложных интерфейсов, где каждый ререндер может быть затратным.
  • Для предсказуемого поведения следует помнить, что обновления состояния в React асинхронны (кроме случаев с flushSync), и если нужно обновить состояние на основе предыдущего значения, стоит использовать функциональную форму setState:
    setCount(prevCount => prevCount + 1); // Гарантирует актуальность предыдущего состояния
    

Таким образом, на практике несколько setState в одном синхронном стеке вызовов действительно компонуются в один ререндер, что делает React-приложения более эффективными. Для разработчика это означает, что не нужно вручную оптимизировать такие сценарии — фреймворк делает это автоматически.

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

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

Множественные обновления состояния и их объединение в React

Да, в React при синхронном вызове нескольких вызовов setState подряд в рамках одного и того же события или жизненного цикла, React группирует (batches) эти обновления в один рендер для оптимизации производительности. Это фундаментальное поведение, которое важно понимать для написания эффективных и предсказуемых приложений.

Как работает группировка обновлений

Когда вы вызываете несколько обновлений состояния последовательно, React не выполняет ререндер после каждого вызова. Вместо этого он собирает все обновления и применяет их единовременно, после чего производит один ререндер.

function Counter() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  const handleClick = () => {
    setCount(count + 1); // Первое обновление
    setText('Updated');  // Второе обновление
    // React сгруппирует обновления и выполнит только один ререндер
  };

  console.log('Render'); // Выведется только один раз при клике

  return (
    <button onClick={handleClick}>
      Count: {count}, Text: {text}
    </button>
  );
}

Важные детали реализации

  1. Синхронные vs Асинхронные обновления:

    • В React 17 и ранее группировка работала только для обновлений внутри синхронных React-событий (onClick, onChange) и методов жизненного цикла
    • В React 18 и новее группировка работает по умолчанию для ВСЕХ обновлений, включая асинхронный код, таймауты и промисы
  2. Функциональные обновления: При последовательных обновлениях одного и того же состояния важно использовать функциональную форму setState для получения актуального предыдущего значения:

const handleIncrement = () => {
  // Неправильно - оба вызова получат одинаковое значение count
  setCount(count + 1);
  setCount(count + 1); // Не сработает как ожидается
  
  // Правильно - используем функциональные обновления
  setCount(prevCount => prevCount + 1);
  setCount(prev