> Как правильно указывать зависимости в useEffect в React (React)
Уровень: senior · Роль: frontend · Категория: Технические вопросы
Компании: Иннотех, Точка, EvApps, EPAM, intelsy
Стек: React
> Пример ответа
Короткий ответ
Правильное указание зависимостей в useEffect - это включение всех реактивных значений (props, state, переменных из тела компонента), которые используются внутри эффекта. Пропуск зависимостей приводит к stale closures и багам. Используйте линтер eslint-plugin-react-hooks с правилом exhaustive-deps для автоматической проверки. Если эффект должен выполниться только при монтировании, передавайте пустой массив, но только если внутри нет реактивных значений.
Подробное объяснение
useEffect в React принимает callback и массив зависимостей. React сравнивает предыдущие зависимости с текущими через Object.is и перезапускает эффект при изменении любой из них. Основные правила:
-
Все реактивные значения должны быть в массиве: state, props, переменные, функции или объекты, объявленные внутри компонента. Это включает значения, полученные через
useState,useReducer,useMemo,useCallback, а также пропсы. -
Исключения: refs (через
useRef) и dispatch отuseReducerстабильны и не требуются в зависимостях. Функции, объявленные вне компонента или переданные через props, тоже не реактивны. -
Пустой массив
[]: эффект выполняется только при монтировании и cleanup при размонтировании. Используйте только если внутри нет реактивных значений - иначе получите stale closure, где эффект "запоминает" старые значения. -
Отсутствие массива: эффект выполняется после каждого рендера - обычно это нежелательно из-за производительности.
-
Правильная работа с функциями: если эффект использует функцию, объявленную внутри компонента, её нужно либо включить в зависимости, либо обернуть в
useCallbackсо стабильными зависимостями.
На практике
- Всегда запускайте линтер с правилом
exhaustive-deps. Он подскажет, какие зависимости пропущены. - Если линтер предлагает добавить зависимость, но вы считаете это ненужным - скорее всего, вы ошибаетесь. Пересмотрите логику.
- Для асинхронных операций внутри эффекта используйте паттерн с флагом отмены, чтобы избежать обновления размонтированного компонента.
- Если эффект зависит от props, которые меняются часто, подумайте о debounce или throttle.
- Избегайте включения в зависимости объектов или массивов, создаваемых при каждом рендере - это вызовет бесконечный цикл. Используйте
useMemoили примитивные значения.
Пример кода
JSX// Плохо: пропущена зависимость userIdfunction UserProfile({ userId }) {const [user, setUser] = useState(null);useEffect(() => {fetchUser(userId).then(setUser);}, []); // userId не указан - stale closurereturn <div>{user?.name}</div>;}// Хорошо: все зависимости указаныfunction UserProfile({ userId }) {const [user, setUser] = useState(null);useEffect(() => {let cancelled = false;fetchUser(userId).then(data => {if (!cancelled) setUser(data);});return () => { cancelled = true; };}, [userId]); // userId в зависимостяхreturn <div>{user?.name}</div>;}// Сложный случай: функция в зависимостяхfunction SearchResults({ query, onResult }) {useEffect(() => {search(query).then(onResult);}, [query, onResult]); // onResult может меняться каждый рендер}// Решение: стабилизируем onResult через useCallbackfunction SearchResults({ query, onResult }) {const stableOnResult = useCallback(onResult, []); // если onResult стабиленuseEffect(() => {search(query).then(stableOnResult);}, [query, stableOnResult]);}
Как отвечать на собеседовании
Начните с базового правила: все реактивные значения в зависимостях. Приведите пример stale closure с пропущенной зависимостью. Объясните, почему пустой массив - это не "только при монтировании", а "без зависимостей". Упомяните линтер как обязательный инструмент. Для senior-уровня добавьте про паттерн отмены асинхронных операций, стабилизацию функций через useCallback и проблемы с объектами/массивами в зависимостях. Покажите понимание, когда useEffect не нужен - например, для вычислений на основе state используйте useMemo.
Что проверяет интервьюер
- Понимание механизма сравнения зависимостей через
Object.is. - Осознание проблемы stale closures и способов её избежать.
- Умение работать с асинхронными операциями в эффектах (cleanup, отмена запросов).
- Знание trade-off между разными подходами (пустой массив, без массива, с зависимостями).
- Способность объяснить, почему линтер - это не просто рекомендация, а обязательное правило.
Типичные ошибки
- Пропуск зависимостей "потому что они не меняются": даже если значение кажется стабильным, оно может измениться при ререндере родителя.
- Включение ненужных зависимостей: например, dispatch или ref - они стабильны, но их включение не вредит, хотя избыточно.
- Игнорирование линтера: добавление
// eslint-disable-next-lineбез понимания последствий. - Использование пустого массива для эффекта, который читает props или state: приводит к багам, которые сложно отловить.
- Создание новых объектов/функций при каждом рендере и передача их в зависимости: вызывает бесконечный цикл эффекта.
> Похожие задачи по frontend
Для чего используется React и почему его выбирают
Что такое React Context и как работает хук useContext
Что такое reconciliation в React и как он работает
Что происходит в useEffect при передаче пустого массива, отсутствия зависимостей или конкретных зависимостей
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью