Какие особенности зависимостей useEffect важно учитывать на практике и почему?
Хуки
Зависимости useEffect — то место, где чаще всего ошибаются на практике. Особенности стоит знать наизусть.
Сравнение — поверхностное, по Object.is
React сравнивает каждый элемент массива со значением из прошлого рендера. Примитивы сравниваются честно; объекты, массивы и функции — по ссылке:
const options = { page, size: 20 }; // новая ссылка каждый рендер
useEffect(() => { load(options); }, [options]); // эффект — каждый рендер!Лечение: примитивы в deps ([page]), useMemo/useCallback, или создать объект внутри эффекта.
Полнота обязательна
Всё реактивное, что эффект использует, должно быть в массиве, иначе эффект «замыкает» устаревшие значения (stale closure). ESLint exhaustive-deps ловит; подавление — почти всегда закапывание бага. Легальные способы «сузить» зависимости: функциональные сеттеры (setX((x) => ...)), dispatch, вынесение функций внутрь эффекта.
Что можно не указывать
Стабильные значения: сеттеры useState, dispatch, ref-объект, значения импортов/констант вне компонента.
Ref в зависимостях не работает
ref.current меняется без рендера — эффект не «увидит» изменение. Ref в deps либо бесполезен (объект стабилен), либо сигнал, что нужен state.
Мутации не видны
items.push(x) не меняет ссылку — эффект с [items] не сработает. Только иммутабельные обновления.
Каждая зависимость = перезапуск + cleanup
Перед перезапуском выполняется cleanup предыдущего эффекта. Это удобно (переподключение при смене roomId), но означает: слишком широкие deps — лишние переподключения/запросы.
Коротко для собеседования
Особенности deps: поверхностное сравнение по Object.is (объекты/функции нестабильны — нужен useMemo/useCallback), полнота обязательна (иначе stale closure), сеттеры/dispatch/ref можно не указывать, ref.current как зависимость не работает, мутации не видны, каждый перезапуск сопровождается cleanup.