> Почему второй setState не получает измененное значение state в React и как это исправить (React, TypeScript)
Уровень: middle · Роль: frontend · Категория: Технические вопросы
Компании: ADV/web-engineering
Стек: React, TypeScript
> Пример ответа
Короткий ответ
В React setState - асинхронная операция. При вызове второго setState сразу после первого, React ещё не применил первое изменение, поэтому второй setState работает со старым значением state. Исправляется использованием функциональной формы setState: setCount(prev => prev + 1), которая гарантирует доступ к актуальному состоянию.
Подробное объяснение
React группирует (batches) несколько вызовов setState в один ререндер для оптимизации производительности. Когда вы пишете:
JSXsetCount(count + 1);setCount(count + 1);
Оба вызова используют одно и то же значение count из текущего рендера. React не обновляет state мгновенно - он ставит обновление в очередь и обрабатывает его позже. В результате оба setState работают с одинаковым базовым значением, и финальный state увеличивается только на 1, а не на 2.
Функциональная форма setCount(prev => prev + 1) решает проблему, так как React передаёт в callback актуальное значение state на момент применения обновления. Каждый вызов получает предыдущее значение из очереди обновлений, а не из замкнутого в замыкании.
На практике
В реальных проектах эта проблема чаще всего возникает в обработчиках событий, useEffect или setTimeout, где несколько последовательных обновлений state должны зависеть друг от друга. Типичные сценарии:
- Инкремент счётчика несколько раз подряд
- Toggle состояния с проверкой предыдущего значения
- Обновление объекта или массива на основе предыдущего состояния
Решение - всегда использовать функциональную форму setState, когда новое состояние зависит от предыдущего. Это безопасно и предсказуемо независимо от того, как React группирует обновления.
В React 18 с автоматическим batching проблема менее заметна, но функциональная форма остаётся лучшей практикой для зависимых обновлений.
Пример кода
TSX// Проблема: оба вызова используют одно и то же значение countconst [count, setCount] = useState(0);const handleClick = () => {setCount(count + 1); // использует count = 0setCount(count + 1); // тоже использует count = 0// Результат: count = 1, а не 2};// Решение: функциональная формаconst handleClickFixed = () => {setCount(prev => prev + 1); // prev = 0 → 1setCount(prev => prev + 1); // prev = 1 → 2// Результат: count = 2};
Как отвечать на собеседовании
Начни с ключевой причины - асинхронность setState и batching. Объясни, что React не обновляет state мгновенно, а ставит в очередь. Покажи разницу между объектной и функциональной формой. Упомяни, что в React 18 batching улучшен, но проблема с зависимыми обновлениями остаётся. Закончи рекомендацией всегда использовать функциональную форму для зависимых обновлений.
Что проверяет интервьюер
Интервьюер оценивает понимание асинхронной природы React, механизма batching, разницы между формами setState и умение применять функциональную форму. Также проверяется знание того, как React обрабатывает очередь обновлений и почему замыкания могут давать устаревшие значения.
Типичные ошибки
- Утверждение, что setState синхронный в обработчиках событий
- Использование
setState({...state, key: value})без функциональной формы для зависимых обновлений - Предложение использовать
setTimeoutс нулевой задержкой как решение - Путаница между batching в React 17 и React 18
- Игнорирование проблемы при работе с объектами и массивами в state
> Похожие задачи по frontend
Что означает ошибка too many renders в React и как её исправить
Нужно ли мутировать массив товаров или создавать его копию для обновления состояния в React?
Работали ли вы с React Query?
Почему возникает ошибка при вызове методов subscribe и getState при монтировании компонента?
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью