Какие нюансы useEffect важны на практике и как их применять?
Хуки
Практические нюансы useEffect — то, на чём спотыкаются чаще всего, и то, что отличает уверенное владение хуком.
Время выполнения
Эффект срабатывает после коммита и отрисовки кадра — не блокирует показ. Нужно измерить/поправить DOM до кадра (позиционирование, скролл) — useLayoutEffect.
Зависимости
- Сравнение поверхностное (Object.is): объекты/функции из рендера — «всегда новые», эффект перезапускается каждый рендер. Лечение: примитивы в deps, useMemo/useCallback, перенос значения внутрь эффекта.
- Пропущенная зависимость → устаревшее замыкание: таймер/подписка видят state первого рендера. Лечение: функциональные сеттеры
setX((x) => ...), честные deps, ref для «свежего» значения. - Сеттеры useState, dispatch и ref-объект стабильны — в deps не нужны.
Cleanup
Вызывается перед каждым перезапуском и при размонтировании. Обязателен для подписок, таймеров, соединений, отмены запросов (AbortController/флаг), иначе утечки и гонки.
Асинхронность
useEffect(async () => ...) нельзя: колбэк обязан вернуть функцию или undefined. Асинхронность — внутри, с защитой от гонок.
StrictMode
В dev эффекты монтирования выполняются дважды (mount → cleanup → mount). Двойной запрос — не баг, а проверка идемпотентности и cleanup.
Когда эффект не нужен
Производные данные — вычислять в рендере/useMemo, не «effect + setState». Реакция на клик — в обработчике. Серверные данные — React Query. Цепочки эффектов, триггерящих друг друга, — признак неправильной модели.
Коротко для собеседования
Главное: эффект после отрисовки (для «до кадра» — useLayoutEffect); deps сравниваются по Object.is (нестабильные ссылки и stale closures — главные баги); cleanup обязателен и вызывается перед каждым перезапуском; async напрямую нельзя; StrictMode дублирует монтирование в dev; для вычислений, событий и серверных данных эффект не нужен.