На каких стадиях жизненного цикла React-компонента срабатывает useEffect?
Что проверяют
Понимаете ли вы, когда именно запускается эффект и его очистка, и видите ли разницу между «жизненным циклом» и синхронизацией с внешним миром. Следом обычно спрашивают, почему эффект в разработке срабатывает дважды, или просят найти бесконечный цикл в коде.
Как отвечать
Эффект запускается после того, как React закоммитил изменения в DOM, как правило уже после отрисовки кадра браузером. Когда именно, решает массив зависимостей:
- без массива: после каждого рендера;
- пустой массив
[]: один раз после монтирования; [a, b]: после монтирования и после рендеров, в которых изменилась хотя бы одна зависимость. Сравнение идёт черезObject.is, то есть объекты и функции сравниваются по ссылке.
Функция, которую возвращает эффект, это очистка. Она вызывается перед каждым следующим запуском эффекта (со старыми значениями из замыкания) и при размонтировании. Если сопоставлять с классами, это componentDidMount, componentDidUpdate и componentWillUnmount в одном месте, но точнее думать иначе: эффект держит внешнюю систему в соответствии с текущими пропсами и состоянием.
Хорошо добавить три детали:
StrictMode: в разработке React монтирует, размонтирует и снова монтирует компонент, поэтому эффект с[]отработает дважды. Это проверка, что очистка написана правильно, в продакшене такого нет;useLayoutEffect: срабатывает синхронно до отрисовки, нужен для замеров DOM без мигания;- сервер: при серверном рендере эффекты не выполняются вообще.
Где спотыкаются
Убирают двойной запуск удалением StrictMode или флагом «уже запускали» вместо того, чтобы написать очистку: отписку, clearInterval, AbortController для запроса.
Кладут в зависимости объект или функцию, созданные прямо в теле компонента. Ссылка новая на каждом рендере, эффект вызывает setState, и получается бесконечный цикл. Или наоборот, выкидывают зависимость, заглушив правило react-hooks/exhaustive-deps, и читают устаревшие значения.
Загружают данные без защиты от гонки: ответ на старый запрос приходит позже нового и затирает актуальные данные. Нужен флаг отмены в очистке или библиотека вроде TanStack Query.