Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Виды зависимостей в React Hooks
Массив зависимостей (dependency array) в Hooks — это критический механизм для контроля когда эффект выполняется. Существует три основных вида зависимостей и правила для работы с ними.
1. Пустой массив зависимостей [] — выполнить один раз
Эффект выполнится только один раз при монтировании компонента и очистится при размонтировании.
function MyComponent() {
useEffect(() => {
console.log('Компонент смонтирован');
// Очистка (опционально)
return () => {
console.log('Компонент размонтирован');
};
}, []); // Пустой массив — только один раз
return <div>Hello</div>;
}
Когда использовать:
- Инициализация данных
- Подписка на события (и отписка при размонтировании)
- Загрузка начальных данных с API
// Практический пример: загрузка данных
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetchUser(userId)
.then(setUser);
}, []); // Загружается один раз
return <div>{user?.name}</div>;
}
Опасность: Если компонент переиспользуется с разными props, эффект не обновится!
2. Без массива зависимостей (undefined) — выполнить всегда
Эффект выполнится после каждого рендера (после каждого изменения props или state).
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('Эффект после каждого рендера');
}); // Без массива — выполняется всегда
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
// При каждом клике — логируется два раза
// 1. После клика (setCount)
// 2. После рендера
Когда использовать:
- Синхронизация с DOM (редко в современном React)
- Отладка
Проблема: Это очень редко нужно и обычно вызывает performance issues.
3. С конкретными зависимостями [dep1, dep2, ...] — выполнить при изменении
Эффект выполнится только если одна из зависимостей изменилась. React сравнивает через Object.is().
function SearchUsers({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
console.log('Ищу пользователей для:', query);
searchAPI(query)
.then(setResults);
}, [query]); // Выполняется только если query изменился
return <ul>{results.map(r => <li key={r.id}>{r.name}</li>)}</ul>;
}
Пример с несколькими зависимостями:
function UserProfile({ userId, includeDetails }) {
const [user, setUser] = useState(null);
useEffect(() => {
const endpoint = includeDetails
? `/api/users/${userId}/full`
: `/api/users/${userId}`;
fetch(endpoint)
.then(r => r.json())
.then(setUser);
}, [userId, includeDetails]); // Обе зависимости
// Выполнится если userId ИЛИ includeDetails изменился
return <div>{user?.name}</div>;
}
Как React сравнивает зависимости?
React использует Object.is() для сравнения:
// Примитивы сравниваются по значению
Object.is(5, 5) // true
Object.is('hello', 'hello') // true
Object.is(true, true) // true
Object.is(null, null) // true
Object.is(undefined, undefined) // true
// Объекты сравниваются по ссылке (identity)
const obj1 = { name: 'John' };
const obj2 = { name: 'John' };
Object.is(obj1, obj2) // false — разные объекты!
Object.is(obj1, obj1) // true — один объект
Практическая проблема с объектами:
function MyComponent() {
const [count, setCount] = useState(0);
// ❌ Проблема: новый объект при каждом рендере
const config = { enabled: true, value: count };
useEffect(() => {
console.log('Config изменился:', config);
}, [config]); // Выполнится при КАЖДОМ рендере
// Потому что config — новый объект каждый раз!
return (
<div>
Count: {count}
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
// ✅ Решение 1: переместить объект выше
const config = { enabled: true }; // Вне компонента
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('Config изменился:', config);
}, [config]); // Теперь config одинаковый
}
// ✅ Решение 2: useMemo для стабилизации
function MyComponent() {
const [count, setCount] = useState(0);
const config = useMemo(() => ({
enabled: true,
value: count
}), [count]); // Только если count изменился
useEffect(() => {
console.log('Config изменился:', config);
}, [config]); // Только если config по-настоящему изменился
return <div>Count: {count}</div>;
}
Проблемы с зависимостями и их решения
Проблема 1: Забытая зависимость
// ❌ Ошибка
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1); // Забыли включить в зависимости
}, 1000);
return () => clearInterval(interval);
}, []); // Пусто, но seconds используется!
return <div>Time: {seconds}</div>;
}
// ✅ Исправить: использовать функциональный update
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1); // Функция не нуждается в зависимостях
}, 1000);
return () => clearInterval(interval);
}, []); // Правильно!
return <div>Time: {seconds}</div>;
}
Проблема 2: Лишние зависимости
// ❌ Избыточно
function Component() {
const [count, setCount] = useState(0);
const double = count * 2;
useEffect(() => {
console.log('Double changed:', double);
}, [count, double]); // double зависит от count!
// Лучше включить только count
}
// ✅ Правильно
function Component() {
const [count, setCount] = useState(0);
const double = count * 2;
useEffect(() => {
console.log('Count changed:', count);
// Вычисляем double внутри
const d = count * 2;
}, [count]); // Только count
}
Проблема 3: Стабилизация с useCallback
function Parent() {
const [count, setCount] = useState(0);
// ❌ Новая функция при каждом рендере
const handleClick = () => {
console.log('Count:', count);
};
return <Child onClick={handleClick} />; // Child переренда
}
// ✅ useCallback для стабилизации
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('Count:', count);
}, [count]); // Новая функция только если count изменился
return <Child onClick={handleClick} />;
}
Правило ESLint: exhaustive-deps
ESLint правило react-hooks/exhaustive-deps поможет найти ошибки:
// Правило будет ругаться
useEffect(() => {
console.log(user); // ← используется
}, [userId]); // ← но user не в зависимостях!
// Решение
useEffect(() => {
console.log(user);
}, [user]); // Или [userId] если user вычисляется из userId
Резюме
| Зависимости | Когда выполняется | Использование |
|---|---|---|
[] | Один раз при монтировании | Инициализация, подписки |
undefined | После каждого рендера | Редко, обычно ошибка |
[dep1, dep2] | Если dep изменилась | Основной случай |
Правила:
- Включай все переменные, которые используются в эффекте
- Не забывай про Object.is() — объекты сравниваются по ссылке
- Используй функциональные updates чтобы избежать зависимостей от state
- Включи ESLint правило exhaustive-deps
- Используй useMemo и useCallback для стабилизации зависимостей