← Назад к вопросам
Как работают зависимости в React?
2.0 Middle🔥 131 комментариев
#React#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работают зависимости в React
Dependency array (массив зависимостей) - это критическая часть React хуков. Это механизм, который сообщает React, когда нужно пересчитать результаты хуков. Неправильное использование зависимостей - частая причина багов и утечек памяти.
Базовая концепция
Массив зависимостей используется в хуках: useState, useEffect, useMemo, useCallback, useReducer.
import { useEffect, useMemo, useCallback } from 'react';
function Component({ userId }) {
// Пересчитается ТОЛЬКО когда userId изменится
useEffect(() => {
console.log('userId changed to:', userId);
}, [userId]);
// Пересчитается когда userId или count изменится
useMemo(() => {
return userId + count;
}, [userId, count]);
// Создаст новую функцию только когда userId изменится
useCallback(() => {
api.updateUser(userId);
}, [userId]);
}
Как React отслеживает зависимости
React использует Object.is для сравнения значений между рендерами:
// Object.is - строгое сравнение
console.log(Object.is(3, 3)); // true
console.log(Object.is('hello', 'hello')); // true
console.log(Object.is({}, {})); // false (разные объекты!)
console.log(Object.is(NaN, NaN)); // true (отличие от ===)
// React сравнивает каждый элемент массива
const deps1 = [userId, count];
const deps2 = [userId, count];
// React считает их РАЗНЫМИ, т.к. это разные массивы!
const deps3 = deps1;
// А это тот же массив - React считает их ОДИНАКОВЫМИ
Правила использования зависимостей
1. Правильно указывать ВСЕ зависимости
// ПЛОХО - забыли userId в зависимостях
function Component({ userId }) {
useEffect(() => {
const timer = setInterval(() => {
console.log('userId:', userId); // Всегда будет старое значение!
}, 1000);
return () => clearInterval(timer);
}, []); // Пустой массив = эффект выполнится один раз
}
// ПРАВИЛЬНО
function Component({ userId }) {
useEffect(() => {
const timer = setInterval(() => {
console.log('userId:', userId);
}, 1000);
return () => clearInterval(timer);
}, [userId]); // userId добавлена
}
2. Пустой массив = выполнить один раз
function Component() {
// Выполнится один раз при монтировании
useEffect(() => {
console.log('Component mounted');
return () => console.log('Component unmounted');
}, []);
return <div>Component</div>;
}
3. Без массива = выполнить КАЖДЫЙ рендер
function Component() {
// ОПАСНО! Выполнится при каждом рендере
useEffect(() => {
console.log('This runs on EVERY render');
}); // Нет массива зависимостей!
return <div>Component</div>;
}
Практические примеры
1. Загрузка данных
function UserProfile({ userId }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Загрузится при изменении userId
useEffect(() => {
let isMounted = true;
const fetchUser = async () => {
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
// Проверяем, еще ли компонент смонтирован
if (isMounted) {
setUser(data);
setError(null);
}
} catch (err) {
if (isMounted) {
setError(err.message);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchUser();
// Очистка при размонтировании или изменении userId
return () => {
isMounted = false;
};
}, [userId]); // ВАЖНО: userId включен
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <div>{user?.name}</div>;
}
2. Дебоунс поиска
function SearchUsers() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<User[]>([]);
// Дебоунс с useEffect
useEffect(() => {
if (!query) {
setResults([]);
return;
}
// Задержка выполнения
const timer = setTimeout(async () => {
const data = await fetch(`/api/search?q=${query}`)
.then(r => r.json());
setResults(data);
}, 500);
// Очистка предыдущего timer при изменении query
return () => clearTimeout(timer);
}, [query]); // Пересчитается при изменении query
return (
<>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<ul>
{results.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
}
3. Подписка на событие
function MouseTracker() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
setPosition({ x: e.clientX, y: e.clientY });
};
// Подписаться на событие
window.addEventListener('mousemove', handleMouseMove);
// Очистить подписку
return () => {
window.removeEventListener('mousemove', handleMouseMove);
};
}, []); // Только один раз при монтировании
return <div>X: {position.x}, Y: {position.y}</div>;
}
Проблемы с зависимостями
Проблема 1: Бесконечный цикл
function Component() {
const [count, setCount] = useState(0);
const [data, setData] = useState(null);
// ПЛОХО - бесконечный цикл!
useEffect(() => {
const config = { count }; // Новый объект каждый раз
// ...
}, [{ count }]); // Новый объект в зависимостях!
// ПРАВИЛЬНО
useEffect(() => {
// ...
}, [count]); // Примитивное значение
}
Проблема 2: Утечка памяти
// ПЛОХО - подписка никогда не удаляется
function Observer() {
useEffect(() => {
const unsubscribe = store.subscribe(() => {
console.log('Store changed');
});
// Забыли вернуть cleanup!
}, []);
}
// ПРАВИЛЬНО
function Observer() {
useEffect(() => {
const unsubscribe = store.subscribe(() => {
console.log('Store changed');
});
return () => unsubscribe(); // Очистка
}, []);
}
Проблема 3: Объекты и массивы
function Parent() {
// ПЛОХО - новый объект каждый рендер
const config = { timeout: 1000 };
return <Child config={config} />;
}
function Child({ config }) {
useEffect(() => {
console.log('Effect runs every render!');
}, [config]); // config всегда новый объект
}
// ПРАВИЛЬНО
function Parent() {
const config = useMemo(() => ({ timeout: 1000 }), []);
return <Child config={config} />;
}
function Child({ config }) {
useEffect(() => {
console.log('Effect runs only on mount');
}, [config]); // config один и тот же
}
useCallback vs useMemo
function Component({ userId }) {
// useMemo - мемоизирует результат вычисления
const expensiveValue = useMemo(() => {
return calculateComplexValue(userId);
}, [userId]);
// useCallback - мемоизирует функцию
const handleClick = useCallback(() => {
api.updateUser(userId);
}, [userId]);
return (
<>
<div>{expensiveValue}</div>
<button onClick={handleClick}>Update</button>
</>
);
}
ESLint правила
Используй плагин eslint-plugin-react-hooks:
{
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
Это будет предупреждать о забытых зависимостях:
// ESLint предупредит: 'userId' is missing from dependencies
useEffect(() => {
fetchUser(userId);
}, []); // userId забыт!
Checklist для правильной работы с зависимостями
- Включи ВСЕ переменные, используемые в эффекте
- Не включай функции, если они не меняются (обверни в useCallback)
- Не создавай объекты внутри компонента для зависимостей
- Используй пустой массив только для эффектов при монтировании
- Всегда добавляй cleanup функцию для подписок
- Используй ESLint плагин для автоматической проверки