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

Зачем нужен Batching?

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

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

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

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

Batching в React: Назначение и применение

Batching — это оптимизационный механизм в React, который группирует множество обновлений состояния в один re-render цикл. Это критически важная техника для производительности современных React приложений.

Что такое Batching?

Batching (пакетирование) — это процесс группировки нескольких обновлений состояния (setState вызовов) в один render цикл вместо обработки каждого обновления отдельно.

Без Batching:

// Каждое обновление — отдельный render
setState(a); // render 1
setState(b); // render 2
setState(c); // render 3
// Итого: 3 re-renders

С Batching:

// Все обновления группируются в один render
setState(a);
setState(b);
setState(c);
// Итого: 1 re-render

История Batching в React

React 16 и 17: Batching работал только внутри React event handlers:

// В React event handler — БЕЗ батчинга!
function handleClick() {
  setCount(c => c + 1);
  setName('John');
  // До React 18: 2 render
  // React 18: 1 render (с Automatic Batching)
}

Но вне React event handlers батчинга не было:

// В Promise — БЕЗ батчинга (старое поведение)
setTimeout(() => {
  setCount(c => c + 1);  // render 1
  setName('John');       // render 2
}, 0);

React 18: По умолчанию весь код батчится (Automatic Batching)!

// React 18 — это БЕЗ батчинга по умолчанию!
setTimeout(() => {
  setCount(c => c + 1);
  setName('John');
  // React 18: 1 render (батчинг работает везде!)
}, 0);

Практические примеры

Пример 1: Event Handler

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');

  const handleUpdate = () => {
    // Все три setState'а батчятся в один re-render
    setCount(c => c + 1);
    setName('John');
    setEmail('john@example.com');
  };

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

  return (
    <div>
      <p>Count: {count}</p>
      <p>Name: {name}</p>
      <p>Email: {email}</p>
      <button onClick={handleUpdate}>Update All</button>
    </div>
  );
}

Пример 2: Асинхронный код

function FormSubmit() {
  const [loading, setLoading] = useState(false);
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  const handleSubmit = async () => {
    // React 18: все обновления батчатся!
    setLoading(true);
    setError(null);
    setData(null);
    // До React 18: 3 re-render
    // React 18: 1 re-render

    try {
      const response = await fetch('/api/data');
      const result = await response.json();
      
      // Все три setState'а также батчатся
      setLoading(false);
      setData(result);
      setError(null);
      // До React 18: 3 re-render
      // React 18: 1 re-render
    } catch (err) {
      setLoading(false);
      setError(err.message);
      setData(null);
    }
  };

  return (
    <div>
      {loading && <p>Загрузка...</p>}
      {data && <p>{JSON.stringify(data)}</p>}
      {error && <p style={{ color: 'red' }}>Ошибка: {error}</p>}
      <button onClick={handleSubmit}>Отправить</button>
    </div>
  );
}

Пример 3: Отключение Batching (когда нужно)

import { flushSync } from 'react-dom';

function Form() {
  const [input, setInput] = useState('');
  const [list, setList] = useState([]);

  const handleSubmit = () => {
    // Если нужен render ПОСЛЕ первого setState
    flushSync(() => {
      setInput(''); // Это произведёт немедленный render
    });
    
    // Теперь можно использовать обновлённое значение
    setList([...list, input]);
  };

  return (
    <div>
      <input
        value={input}
        onChange={(e) => setInput(e.target.value)}
      />
      <button onClick={handleSubmit}>Add</button>
      <ul>
        {list.map((item, i) => <li key={i}>{item}</li>)}
      </ul>
    </div>
  );
}

Почему Batching важен?

1. Производительность

// Без батчинга
setState1(); // render 1 → DOM update
setState2(); // render 2 → DOM update
setState3(); // render 3 → DOM update
// 3 render цикла, 3 DOM обновления, много вычислений

// С батчингом
setState1();
setState2();
setState3();
// 1 render цикл, 1 DOM обновление

Представьте форму с 10 полями — батчинг даёт 10x ускорение!

2. Консистентность состояния

function UserForm() {
  const [firstName, setFirstName] = useState('');
  const [lastName, setLastName] = useState('');

  const loadUser = async (userId) => {
    const user = await fetchUser(userId);
    
    // Оба setState'а батчатся — состояние будет консистентно
    setFirstName(user.firstName);
    setLastName(user.lastName);
    // Компонент не покажет firstName без lastName
  };

  return <div>{firstName} {lastName}</div>;
}

3. Предсказуемость

В React 18 батчинг работает везде одинаково, что делает код более предсказуемым.

Более сложный пример

import { useState } from 'react';
import { flushSync } from 'react-dom';

function ChatApp() {
  const [messages, setMessages] = useState([]);
  const [input, setInput] = useState('');
  const [isTyping, setIsTyping] = useState(false);

  const handleSendMessage = async () => {
    // Батчятся: очищение input и добавление сообщения
    setInput('');
    setMessages(m => [...m, { id: Date.now(), text: input, author: 'me' }]);
    
    setIsTyping(true);

    try {
      const response = await fetch('/api/chat', {
        method: 'POST',
        body: JSON.stringify({ message: input })
      });
      
      const reply = await response.json();
      
      // Батчатся: добавление ответа и отключение typing
      setMessages(m => [...m, { id: Date.now(), text: reply.text, author: 'bot' }]);
      setIsTyping(false);
      // Только 1 re-render!
    } catch (error) {
      setIsTyping(false);
    }
  };

  return (
    <div>
      <div className="messages">
        {messages.map(msg => (
          <div key={msg.id} className={`message ${msg.author}`}>
            {msg.text}
          </div>
        ))}
        {isTyping && <div className="typing">Бот печатает...</div>}
      </div>
      <div className="input-area">
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Введите сообщение..."
        />
        <button onClick={handleSendMessage}>Отправить</button>
      </div>
    </div>
  );
}

Автоматический Batching в React 18

// React 18: все эти случаи батчатся автоматически

// 1. Event handlers
button.onClick = () => {
  setState1();
  setState2();
  // 1 render
};

// 2. Promises
fetch('/api').then(() => {
  setState1();
  setState2();
  // 1 render (раньше было 2)
});

// 3. setTimeout
setTimeout(() => {
  setState1();
  setState2();
  // 1 render (раньше было 2)
}, 1000);

// 4. Event listeners
window.addEventListener('scroll', () => {
  setState1();
  setState2();
  // 1 render
});

Когда нужен flushSync?

function SearchResults() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const inputRef = useRef(null);

  const search = (newQuery) => {
    // Нужно обновить input ДО начала поиска
    flushSync(() => setQuery(newQuery));
    
    // Теперь inputRef.current.value точно обновлён
    performSearch(newQuery);
    
    setResults([...]);
  };

  return (
    <input ref={inputRef} value={query} onChange={(e) => search(e.target.value)} />
  );
}

Заключение

Batching — это критически важная оптимизация, которая делает React приложения намного быстрее. React 18 внедрил Automatic Batching по умолчанию, что означает, что разработчики получают высокую производительность без дополнительных усилий. Понимание батчинга помогает писать более эффективный и предсказуемый код.