Как dependency array влияет на поведение useEffect?
Что такое dependency array
Dependency array (массив зависимостей) — это второй аргумент хука useEffect, который контролирует, когда эффект будет запущен повторно.
Варианты поведения
1. Без массива зависимостей
Если второй аргумент не передан, эффект запускается после каждого рендера компонента.
useEffect(() => {
// Выполняется после каждого рендера
console.log('компонент обновился');
});
Это редко бывает нужным и может вызвать проблемы с производительностью.
2. Пустой массив зависимостей
Эффект выполняется один раз после первого монтирования компонента и больше не повторяется.
useEffect(() => {
// Выполняется только при монтировании
fetchUserData();
return () => {
// Функция очистки вызывается при размонтировании
cleanup();
};
}, []);
Аналог componentDidMount из классовых компонентов.
3. Массив с зависимостями
Эффект запускается при монтировании и каждый раз, когда изменяется хотя бы одна из перечисленных зависимостей.
const [userId, setUserId] = useState(1);
useEffect(() => {
// Вызывается при монтировании и при каждом изменении userId
fetchUserById(userId);
}, [userId]);
Как React сравнивает зависимости
React использует алгоритм Object.is для сравнения значений между рендерами. Это означает, что:
- Примитивы (числа, строки, булевы) сравниваются по значению
- Объекты и массивы сравниваются по ссылке
useEffect(() => {
// Этот эффект будет вызываться на каждом рендере,
// так как объект options создаётся заново при каждом рендере
fetchData(options);
}, [options]); // options = { page: 1 } — новая ссылка каждый раз
Чтобы избежать этого, нужно стабилизировать объект через useMemo или вынести его за пределы компонента.
Функция очистки
При смене зависимостей React сначала вызывает функцию очистки предыдущего эффекта, затем запускает новый.
useEffect(() => {
const subscription = subscribe(channelId);
return () => {
// Вызывается перед следующим запуском эффекта
// или при размонтировании компонента
subscription.unsubscribe();
};
}, [channelId]);
Правило exhaustive-deps
ESLint-плагин eslint-plugin-react-hooks предупреждает, если в эффекте используются переменные, не указанные в dependency array. Игнорировать эти предупреждения не рекомендуется — это обычно признак логической ошибки.
const [count, setCount] = useState(0);
useEffect(() => {
// ESLint предупредит: count не указан в зависимостях
document.title = `Счётчик: ${count}`;
}, []); // Нужно добавить count
Что хочет услышать интервьюер
Кандидат объясняет три сценария: без массива, с пустым массивом и с зависимостями
Понимание того, что React использует Object.is для сравнения зависимостей
Знание о функции очистки и когда она вызывается
Осознание проблемы со ссылочными типами (объекты, массивы, функции) в зависимостях
Упоминание правила exhaustive-deps или eslint-плагина
Пример: Три варианта dependency array
import { useState, useEffect } from 'react';
function ExampleComponent({ userId }: { userId: number }) {
const [data, setData] = useState(null);
// 1. Без массива — запускается после каждого рендера
useEffect(() => {
console.log('каждый рендер');
});
// 2. Пустой массив — только при монтировании
useEffect(() => {
console.log('только при монтировании');
return () => {
console.log('при размонтировании');
};
}, []);
// 3. С зависимостью — при монтировании и при изменении userId
useEffect(() => {
let cancelled = false;
async function load() {
const result = await fetchUser(userId);
// Проверяем флаг, чтобы избежать обновления после размонтирования
if (!cancelled) {
setData(result);
}
}
load();
return () => {
// Отменяем устаревший запрос при смене userId
cancelled = true;
};
}, [userId]);
return <div>{JSON.stringify(data)}</div>;
}
Пример: Проблема со ссылочными зависимостями
import { useState, useEffect, useMemo } from 'react';
function BadExample({ page }: { page: number }) {
// Объект создаётся заново при каждом рендере — бесконечный цикл!
const options = { page, limit: 10 };
useEffect(() => {
fetchData(options);
}, [options]); // Всегда новая ссылка на options
}
function GoodExample({ page }: { page: number }) {
// Стабилизируем объект через useMemo
const options = useMemo(() => ({ page, limit: 10 }), [page]);
useEffect(() => {
fetchData(options);
}, [options]); // Теперь эффект сработает только при смене page
}
Типичные ошибки
Путают пустой массив [] с отсутствием массива и не понимают разницу в поведении
Добавляют объекты или функции в зависимости без стабилизации через useMemo/useCallback, создавая бесконечный цикл рендеров
Намеренно скрывают зависимости в пустом массиве, чтобы запустить эффект только раз, хотя внутри используются изменяемые значения
Не возвращают функцию очистки при подписках и таймерах, что приводит к утечкам памяти
Игнорируют предупреждения eslint-plugin-react-hooks вместо того чтобы разобраться в причине


