Как работает обновление состояния при нескольких вызовах setState в React?
Что проверяют
Знаете ли вы, что сеттер состояния не меняет переменную сразу, а ставит обновление в очередь, и как React объединяет такие вызовы. Следом обычно спрашивают, что изменил автоматический батчинг в React 18 и чем функция-апдейтер отличается от передачи значения.
Как отвечать
setState в классах и сеттер из useState кладут обновление в очередь, а React применяет всю очередь при следующем рендере. Внутри текущего обработчика count остаётся тем снимком, с которым компонент отрендерился. Дальше по пунктам:
- батчинг: несколько вызовов подряд дают один перерендер. С React 18 и
createRootэто работает везде: в обработчиках,setTimeout, промисах, нативных событиях. До 18 версии объединялись только вызовы из событий React; - значение против апдейтера:
setCount(count + 1)три раза подряд даст+1, все три вызова читают один снимок.setCount(c => c + 1)три раза даст+3: апдейтеры выполняются по очереди, каждый получает результат предыдущего; - слияние:
this.setStateповерхностно сливает объект с текущим состоянием. Сеттер изuseStateничего не сливает и заменяет значение целиком, поэтому пишутsetForm(prev => ({ ...prev, name })); - пропуск рендера: если новое значение совпадает со старым по
Object.is, React может не перерисовывать компонент.
Если DOM нужно обновить немедленно, например чтобы прокрутить к только что добавленному элементу, есть flushSync из react-dom. Это исключение, а не приём на каждый день.
Где спотыкаются
Логируют состояние сразу после сеттера, видят старое значение и объясняют это «асинхронностью». Точнее сказать так: в этом рендере переменная не меняется, новое значение появится в следующем.
Переносят на хуки привычку классов и передают частичный объект. Остальные поля состояния при этом пропадают.
Мутируют массив и передают ту же ссылку: items.push(x); setItems(items). По Object.is значение не изменилось, и экран не обновится.