Что происходит при клике на кнопку, вызывающую изменение состояния в React?
Что проверяют
Можете ли вы проследить полный путь от события до пикселей на экране. Ответ показывает, понимаете ли вы, что рендер это вызов функции компонента, а не обновление DOM.
Как отвечать
Разложите цепочку по шагам:
- событие: React не вешает обработчик на саму кнопку. Он слушает события на корневом контейнере приложения и по всплытию находит нужный компонент, это делегирование. Обработчику приходит синтетическое событие, обёртка над нативным с одинаковым поведением в разных браузерах;
- обновление состояния: вызов
setStateизuseStateне меняет переменную немедленно, а планирует обновление. Читать состояние сразу после вызова бесполезно, там ещё старое значение; - пакетирование: несколько вызовов внутри одного обработчика (и, начиная с React 18, внутри промисов и таймеров тоже) собираются в одно обновление, поэтому компонент перерисуется один раз, а не по разу на каждый
setState; - рендер: React заново вызывает функцию компонента и получает новое дерево элементов. Это чистое вычисление, DOM ещё не трогается;
- сверка: новое дерево сравнивается с предыдущим. По типу элемента и
keyReact решает, какие узлы переиспользовать, какие создать и какие удалить; - коммит: в реальный DOM вносится только разница. После этого синхронно выполняются
useLayoutEffect, браузер рисует кадр, затем запускаютсяuseEffect.
Полезно добавить: если новое значение состояния равно прежнему (сравнение через Object.is), React может пропустить перерисовку, а обновление, начатое внутри startTransition, считается неприоритетным и может быть прервано более срочным.
Где спотыкаются
Ждут, что состояние обновится сразу в той же функции.
Обновляют по старому значению вместо функции: два setCount(count + 1) подряд дадут плюс один, а setCount((c) => c + 1) плюс два.
Мутируют состояние на месте: arr.push(x) и затем setArr(arr). Ссылка та же, изменение может не заметиться, нужен новый массив.
Путают рендер и коммит: «перерисовался компонент» не означает, что DOM изменился.