Вы пишете красивый компонент на React, используете хук useEffect для побочных эффектов, и вдруг — красная подсветка в консоли: «React Hook useEffect has a missing dependency». Знакомо? Эта ошибка преследует разработчиков от новичков до опытных, заставляя задуматься о фундаментальных принципах работы React. Давайте разберемся, почему она возникает, как правильно ее исправлять и когда можно (осторожно!) игнорировать.
Что такое useEffect и при чем здесь зависимости?
Хук useEffect — это механизм React для выполнения побочных эффектов: запросов к API, подписок на события, работы с DOM. Второй аргумент useEffect — массив зависимостей — определяет, когда эффект должен выполняться заново. Если массив пуст — эффект выполнится один раз после монтирования. Если в массиве есть переменные — эффект будет запускаться при их изменении.
React сравнивает зависимости по ссылке (reference equality), а не по значению. Это важно для объектов и функций.
Почему появляется ошибка missing dependency?
ESLint с плагином правил React Hooks анализирует ваш код и обнаруживает, что внутри useEffect используются переменные из внешней области видимости (пропсы, состояние, контекст), которые не указаны в массиве зависимостей. Это потенциально опасная ситуация, так как эффект может работать со «устаревшими» значениями.
Типичный пример проблемы
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser);
}, []); // ❌ ESLint предупредит: missing dependency 'userId'
}
Здесь эффект зависит от userId, но при его изменении не выполнится заново — профиль пользователя не обновится.
Стратегии решения: от правильных до спорных
1. Добавить зависимость (правильный путь)
Просто включите все используемые переменные в массив:
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(setUser);
}, [userId]); // ✅ Теперь эффект выполнится при изменении userId
2. Использовать useCallback и useMemo для функций и объектов
Если зависимость — функция или сложный объект, создаваемый внутри компонента, их нужно мемоизировать:
const fetchUser = useCallback(() => {
return fetch(`/api/users/${userId}`).then(res => res.json());
}, [userId]);
useEffect(() => {
fetchUser().then(setUser);
}, [fetchUser]); // ✅ fetchUser мемоизирована
Не мемоизированные функции и объекты создаются заново при каждом рендере, что приводит к бесконечным циклам эффектов.
3. Переписать логику, чтобы избежать зависимости
Иногда можно вынести переменную внутрь эффекта или использовать реф:
useEffect(() => {
let isMounted = true;
// Логика с isMounted
return () => { isMounted = false; };
}, []); // ✅ Нет внешних зависимостей
4. Дисклеймер: отключение линтера (используйте осторожно!)
Можно отключить правило для конкретной строки, но это должно быть исключением:
useEffect(() => {
// Логика, которая точно должна выполниться один раз
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Делайте это только когда уверены, что эффект действительно не зависит от изменяющихся значений (например, инициализация аналитики).
Распространенные ловушки и антипаттерны
- Бесконечные циклы: Эффект изменяет зависимость, которая вызывает его повторный запуск.
- «Устаревшие» замыкания: Эффект «запоминает» старые значения переменных, если они не указаны в зависимостях.
- Избыточные запросы: Слишком много зависимостей, вызывающих частые повторные выполнения.
FAQ: Часто задаваемые вопросы
Почему React заставляет нас указывать все зависимости?
Это гарантирует, что эффект всегда работает с актуальными данными и предотвращает трудноуловимые баги, связанные с устаревшими значениями (stale closures).
Что делать, если зависимость — функция, создаваемая внутри компонента?
Оберните ее в useCallback с ее собственными зависимостями или объявите непосредственно внутри useEffect, если это возможно.
Можно ли использовать пустой массив зависимостей?
Да, но только для эффектов, которые должны выполниться один раз при монтировании и точно не зависят ни от каких пропсов или состояния.
Почему иногда добавление зависимостей вызывает бесконечный цикл?
Потому что эффект изменяет значение, которое является его зависимостью (например, устанавливает состояние, которое триггерит повторный запуск). Нужно пересмотреть логику или использовать условия внутри эффекта.
Обязательно ли всегда следовать правилу exhaustive-deps?
Почти всегда. Игнорирование правила — это осознанный риск, который должен быть четко обоснован в комментарии к коду.