Зачем React ожидает выполнения всех действий пользователя для рендера?
Рендеринг и производительность
Речь о батчинге (группировке) обновлений: React намеренно не рендерит после каждого setState, а накапливает обновления и выполняет один рендер на всю пачку.
Что происходит
const handleClick = () => {
setCount((c) => c + 1); // рендера ещё нет
setFlag(true); // и ещё нет
setUser(newUser); // и теперь — ОДИН рендер на все три
};Если бы каждый setState рендерил немедленно: три рендера, три диффа, три возможных правки DOM — и промежуточные состояния (count обновился, flag ещё нет) мелькали бы на экране.
Зачем это нужно
- Производительность: один цикл рендер→дифф→коммит вместо N; меньше reflow.
- Консистентность: пользователь не видит «полуобновлённых» состояний — UI переходит из старого состояния сразу в новое целиком.
- Обработчик события завершится полностью до рендера — все связанные обновления гарантированно попадают в один кадр.
Эволюция: React 17 → 18
- До React 18 батчинг работал только в React-обработчиках событий; в setTimeout/промисах/нативных слушателях каждый setState рендерил отдельно.
- React 18 (createRoot) — автоматический батчинг везде: таймеры, промисы, fetch-колбэки.
// React 18: один рендер даже здесь
setTimeout(() => {
setA(1);
setB(2);
}, 100);- Нужен синхронный рендер немедленно (редко — измерения DOM) —
flushSync(() => setX(...))из react-dom.
Следствие для кода
setState «не срабатывает сразу»: значение в текущем замыкании не меняется; читать «обновлённый» state сразу после сеттера нельзя — реагировать на изменения нужно эффектами или вычислением при следующем рендере.
Коротко для собеседования
Это батчинг: React собирает все setState синхронного блока (обработчика, а с React 18 — любого контекста) и делает один рендер — ради производительности (один дифф/коммит) и консистентности UI без промежуточных состояний. Форсировать немедленный рендер можно flushSync, но это исключение.