← Назад к вопросам
Почему не нужно использовать useEffect без массива зависимостей?
1.2 Junior🔥 101 комментариев
#React
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
useEffect без массива зависимостей в React
Основная проблема
useEffect без массива зависимостей означает, что эффект будет запускаться после каждого render-а компонента. Это приводит к бесконечным циклам, проблемам производительности и непредсказуемому поведению.
Как это работает
// ПРОБЛЕМНЫЙ КОД — useEffect без зависимостей
function ProblematicComponent() {
const [data, setData] = useState(null);
useEffect(() => {
// Этот код запустится ПОСЛЕ КАЖДОГО RENDER-А
fetch("/api/data")
.then(res => res.json())
.then(result => setData(result));
// setData триггерит re-render
// re-render триггерит useEffect снова
// = БЕСКОНЕЧНЫЙ ЦИКЛ!
}); // <- ни массива зависимостей!
return <div>{data?.title}</div>;
}
// Цикл:
// 1. Component render
// 2. useEffect запускается
// 3. setData(result) триггерит re-render
// 4. useEffect запускается ещё раз
// 5. Шаг 3-4 повторяются бесконечно
// 6. API получает тысячи запросов!
Почему это плохо
1. Бесконечные циклы
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
// Запускается после каждого render-а
setCount(count + 1); // Вызывает re-render
}); // Без зависимостей!
return <div>{count}</div>; // Render снова → useEffect снова → ...
}
// Результат:
// count: 0 → 1 → 2 → 3 → 4 → ... → 999 → максимум call stack
// Страница падает с ошибкой!
2. Нежелательные API запросы
function ProductList() {
const [products, setProducts] = useState([]);
useEffect(() => {
// Запускается после КАЖДОГО render-а
fetch("/api/products").then(res => res.json());
}); // Без зависимостей
// Результат:
// 1. Initial render → API call 1
// 2. Re-render → API call 2
// 3. Re-render → API call 3
// ... за 1 секунду: 100+ запросов!
// Сервер думает что это DDoS
}
4. Утечки памяти (memory leaks)
function ChatComponent() {
const [messages, setMessages] = useState([]);
useEffect(() => {
// Запускается после каждого render-а
// Но cleanup не вызывается перед следующим effet-ом!
const socket = io("ws://chat");
socket.on("message", msg => setMessages(prev => [...prev, msg]));
// НЕ ВЫЗЫВАЕТСЯ cleanup!
// return () => socket.disconnect();
}); // Без зависимостей
// Результат:
// 1. Component render → socket создан
// 2. Сообщение приходит → setMessages → re-render
// 3. useEffect запускается снова → НОВЫЙ socket создан
// 4. Теперь 2 socket-а слушают одновременно
// 5. Каждая итерация добавляет новый socket
// = утечка памяти, каждое сообщение дублируется
}
5. Непредсказуемое поведение
function FormComponent() {
const [formData, setFormData] = useState({});
const [errors, setErrors] = useState({});
useEffect(() => {
// Вызывается при КАЖДОМ render-е
// Может трудно проследить когда что происходит
console.log("Валидирую форму", formData);
if (!formData.email) setErrors({email: "Required"});
// Или запускается сложный расчёт
// Или отправляется запрос
}); // Без зависимостей
// Проблема: что повторяется?
// - Когда юзер печатает в input?
// - Всегда ли валидация?
// - Когда отправляется запрос?
// Непонятно!
}
Правильное использование useEffect
1. С пустым массивом — только при монтировании
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Запускается ТОЛЬКО при монтировании компонента
setLoading(true);
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data))
.finally(() => setLoading(false));
}, []); // ПУСТОЙ массив зависимостей!
if (loading) return <div>Loading...</div>;
return <div>{user.name}</div>;
}
2. С конкретными зависимостями
function SearchResults({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
// Запускается когда query меняется
if (!query) {
setResults([]);
return;
}
fetch(`/api/search?q=${query}`)
.then(res => res.json())
.then(data => setResults(data));
}, [query]); // Зависим от query!
return (
<ul>
{results.map(r => <li key={r.id}>{r.title}</li>)}
</ul>
);
}
3. С cleanup функцией
function ChatWindow() {
const [messages, setMessages] = useState([]);
useEffect(() => {
const socket = io("ws://chat");
socket.on("message", msg => {
setMessages(prev => [...prev, msg]);
});
// CLEANUP функция — вызывается перед umount или перед следующим effect
return () => {
socket.disconnect();
};
}, []); // Пустой массив — эффект один раз, cleanup при umount
return <div>{messages.map(m => <p key={m.id}>{m.text}</p>)}</div>;
}
4. С множественными зависимостями
function FilteredList({ category, sortBy }) {
const [items, setItems] = useState([]);
useEffect(() => {
// Запускается когда category ИЛИ sortBy меняются
fetch(`/api/items?category=${category}&sort=${sortBy}`)
.then(res => res.json())
.then(data => setItems(data));
}, [category, sortBy]); // Оба параметра — зависимости
return <ul>{items.map(i => <li key={i.id}>{i.name}</li>)}</ul>;
}
Таблица различных вариантов
// 1. БЕЗ МАССИВА ЗАВИСИМОСТЕЙ (ПЛОХО!)
useEffect(() => {
// Запускается после КАЖДОГО render-а
// Может привести к циклам и утечкам памяти
});
// 2. С ПУСТЫМ МАССИВОМ (ХОРОШО для инициализации)
useEffect(() => {
// Запускается только при монтировании
// Cleanup (если есть) вызывается при umount
}, []);
// 3. С ЗАВИСИМОСТЯМИ (ХОРОШО для реактивности)
useEffect(() => {
// Запускается при изменении зависимостей
// Cleanup вызывается перед следующим effect-ом
}, [dep1, dep2]);
// 4. С АБСОЛЮТНО ВСЕМИ ЗАВИСИМОСТЯМИ (ПРАВИЛЬНО)
const memoizedCallback = useCallback(() => {
// ...
}, [dep1, dep2]);
useEffect(() => {
// Вызывает callback только если deps изменились
memoizedCallback();
}, [memoizedCallback]); // Зависим от самого callback!
Частые ошибки
Ошибка 1: Забыли зависимость
// НЕПРАВИЛЬНО
function Component({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
}, []); // ЗАБЫЛИ userId в зависимостях!
// Результат:
// userId меняется (3 → 4)
// useEffect НЕ запускается снова
// Показываем данные старого userId!
}
// ПРАВИЛЬНО
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
}, [userId]); // ✓ Включили зависимость
Ошибка 2: Функция как зависимость без useMemo
// НЕПРАВИЛЬНО — бесконечный цикл
function Component() {
const [data, setData] = useState(null);
const fetchData = () => {
return fetch("/api/data").then(r => r.json());
}; // Новая функция при каждом render-е!
useEffect(() => {
fetchData();
}, [fetchData]); // fetchData всегда "новая" → бесконечный loop
}
// ПРАВИЛЬНО — используем useCallback
function Component() {
const [data, setData] = useState(null);
const fetchData = useCallback(() => {
return fetch("/api/data").then(r => r.json());
}, []); // Функция стабильна
useEffect(() => {
fetchData();
}, [fetchData]); // OK, нет цикла
}
React DevTools подсказка
Если у вас ESLint с eslint-plugin-react-hooks:
// Будет предупреждение:
// "React Hook useEffect has a missing dependency: userId"
// СЛУШАЙТЕ ЭТО ПРЕДУПРЕЖДЕНИЕ!
// Можете отключить с комментарием, но нужна хорошая причина
useEffect(() => {
// ...
}, []); // eslint-disable-next-line react-hooks/exhaustive-deps
// ПЛОХО! Не отключайте без причины!
Практический пример — неправильно и правильно
// НЕПРАВИЛЬНО
function UserSearch() {
const [searchTerm, setSearchTerm] = useState("");
const [results, setResults] = useState([]);
useEffect(() => {
// Запускается при КАЖДОМ render-е!
if (searchTerm) {
fetch(`/api/search?q=${searchTerm}`)
.then(r => r.json())
.then(setResults);
}
}); // ← БЕЗ МАССИВА!
}
// ПРАВИЛЬНО
function UserSearch() {
const [searchTerm, setSearchTerm] = useState("");
const [results, setResults] = useState([]);
useEffect(() => {
// Запускается только когда searchTerm меняется
if (searchTerm) {
fetch(`/api/search?q=${searchTerm}`)
.then(r => r.json())
.then(setResults);
}
}, [searchTerm]); // ← С МАССИВОМ ЗАВИСИМОСТЕЙ
}
Вывод
useEffect без массива зависимостей — это почти всегда ошибка, которая приводит к:
- Бесконечным циклам — падение приложения
- Лишним запросам — нагрузка на сервер
- Утечкам памяти — браузер замедляется
- Непредсказуемому поведению — сложно дебажить
Правильный подход:
[]— эффект при монтировании[deps]— эффект когда зависимости меняются- Всегда включайте все переменные из внешнего scope в массив зависимостей
- Используйте ESLint
react-hooksдля проверки - Слушайте предупреждения компилятора!