Как работает состояние счетчика при ререндере в React?
Что проверяют
Понимаете ли вы, где на самом деле живёт состояние и почему count внутри обработчика не меняется сразу после setCount. Следом почти всегда дают задачку: три вызова setCount(count + 1) подряд, что будет на экране.
Как отвечать
Состояние хранится не в компоненте и не в замыкании, а во внутреннем узле React (fiber), который соответствует месту компонента в дереве. Хуки лежат там списком в порядке вызова, поэтому их нельзя вызывать в условиях и циклах: React сопоставляет их по номеру.
- рендер это снимок: при каждом вызове функции-компонента
useStateвозвращает текущее значение из fiber. Обработчики, созданные в этом рендере, замыкают именно этотcount; - обновление ставится в очередь:
setCountне меняет переменную, а планирует новый рендер. Три вызоваsetCount(count + 1)читают один снимок и дадут +1. Функциональная формаsetCount(c => c + 1)берёт результат предыдущего обновления из очереди и даст +3; - батчинг: с React 18 обновления группируются автоматически везде, включая промисы и
setTimeout, и дают один рендер; - выход без рендера: если новое значение совпадает со старым по
Object.is, React пропускает перерисовку детей.
Отдельно скажите про сброс: состояние привязано к позиции в дереве и типу компонента. Если сменить key или отрендерить на этом месте другой компонент, счётчик начнётся с нуля. Если же просто перерисовался родитель, count сохранится, а начальное значение из useState(0) повторно не применится.
Где спотыкаются
Логируют count сразу после setCount и удивляются старому значению. Новое значение появится только в следующем рендере.
Пишут setInterval(() => setCount(count + 1), 1000) внутри useEffect с пустыми зависимостями. Колбэк замкнул первый снимок, и счётчик застревает на единице. Лечится функциональным обновлением.
Объясняют всё виртуальным DOM. Он отвечает за то, как изменения попадут в реальный DOM, а на вопрос, откуда берётся сохранённый count, отвечают fiber и очередь обновлений.