Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Для чего нужен второй аргумент в useEffect
Второй аргумент в useEffect — это массив зависимостей (dependency array). Он контролирует когда эффект должен быть заново запущен. Это один из самых важных концептов в React, потому что неправильное использование может привести к баги и бесконечным циклам.
Синтаксис useEffect
// Базовый синтаксис
useEffect(() => {
// Этот код выполняется
}, [dependencies]); // Второй аргумент - зависимости
// Он работает как:
// "Выполни этот эффект когда какая-то из зависимостей изменилась"
Три варианта использования
1. Без второго аргумента — срабатывает при каждом рендере
import { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('Эффект срабатывает при КАЖДОМ рендере');
document.title = `Count: ${count}`;
// Это будет выполняться после каждого рендера
}); // НЕТ second аргумента
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
// Клик на кнопку -> рендер -> эффект выполнен
// Еще клик -> рендер -> эффект выполнен снова
// И так каждый раз
Проблема: Может быть очень неэффективно и привести к бесконечным циклам.
2. Пустой массив [] — срабатывает только при монтировании
import { useState, useEffect } from 'react';
export default function UserProfile() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
console.log('Эффект срабатывает ТОЛЬКО ОДИН РАЗ при монтировании');
// Загружаем данные пользователя
fetch('/api/user')
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
});
}, []); // Пустой массив зависимостей
return (
<div>
{loading ? <p>Loading...</p> : <h1>{user.name}</h1>}
</div>
);
}
// Монтирование компонента -> эффект выполнен (загрузка данных)
// Перерендер -> эффект НЕ выполняется
// Размонтирование -> cleanup (если есть)
Идеально для: Инициализации данных, установки слушателей, подписок.
3. С зависимостями [dep1, dep2] — срабатывает когда зависимости изменились
import { useState, useEffect } from 'react';
export default function SearchResults() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
console.log('Эффект срабатывает когда query изменился');
if (query.trim() === '') {
setResults([]);
return;
}
// Выполнить поиск
fetch(`/api/search?q=${query}`)
.then(res => res.json())
.then(data => setResults(data));
}, [query]); // Зависимость от query
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
/>
<ul>
{results.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
</div>
);
}
// query = '' -> монтирование -> эффект (нет поиска)
// Ввод 'react' -> query изменился -> эффект (поиск 'react')
// Ввод 'reacts' -> query изменился -> эффект (новый поиск)
Глубокое объяснение зависимостей
// Как React сравнивает зависимости
const dependencies = [query, userId, token];
// При каждом рендере React делает:
if (
previousDependencies[0] !== dependencies[0] || // query
previousDependencies[1] !== dependencies[1] || // userId
previousDependencies[2] !== dependencies[2] // token
) {
// Эффект должен быть заново выполнен
effect();
}
// Сравнение идет через === (strict equality)
// Примитивы сравниваются по значению
// Объекты сравниваются по ссылке
Частая ошибка: зависимости от объектов
import { useState, useEffect } from 'react';
// ПЛОХО - объект создается каждый рендер
export default function BadExample() {
const [user, setUser] = useState(null);
// config создается при каждом рендере - новая ссылка
const config = {
method: 'GET',
headers: { 'Authorization': 'Bearer token' }
};
useEffect(() => {
// Эффект будет выполняться бесконечно!
// config всегда новый объект -> зависимость изменилась
fetch('/api/user', config)
.then(res => res.json())
.then(data => setUser(data));
}, [config]); // Плохо!
return <div>{user?.name}</div>;
}
// Результат:
// 1. Рендер -> эффект -> fetch -> setState
// 2. Новый рендер -> config новый объект -> эффект -> fetch -> setState
// 3. Бесконечный цикл!
// ХОРОШО - объект вне эффекта или useMemo
function GoodExample() {
const [user, setUser] = useState(null);
// Вариант 1: Константа вне компонента
useEffect(() => {
fetch('/api/user', CONFIG)
.then(res => res.json())
.then(data => setUser(data));
}, []); // Нет зависимостей
return <div>{user?.name}</div>;
}
// Вариант 2: useMemo для стабильной ссылки
import { useMemo } from 'react';
function BetterExample() {
const [user, setUser] = useState(null);
const [token, setToken] = useState('initial-token');
const config = useMemo(() => ({
method: 'GET',
headers: { 'Authorization': `Bearer ${token}` }
}), [token]); // Только перестраивается при изменении token
useEffect(() => {
fetch('/api/user', config)
.then(res => res.json())
.then(data => setUser(data));
}, [config]); // Теперь безопасно
return <div>{user?.name}</div>;
}
Практические примеры
Пример 1: Загрузка данных при изменении ID
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
setError(null);
fetch(`/api/users/${userId}`)
.then(res => {
if (!res.ok) throw new Error('Failed to fetch');
return res.json();
})
.then(data => {
setUser(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, [userId]); // Перезагрузить при изменении userId
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return <h1>{user?.name}</h1>;
}
// userId = 1 -> эффект -> загрузка пользователя 1
// userId = 2 -> эффект -> загрузка пользователя 2
Пример 2: Cleanup - удаление слушателя
import { useState, useEffect } from 'react';
function WindowSize() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
const handleResize = () => {
setWidth(window.innerWidth);
};
// Добавляем слушатель
window.addEventListener('resize', handleResize);
// Cleanup функция - удаляется слушатель
return () => {
console.log('Cleanup: удаляем слушатель');
window.removeEventListener('resize', handleResize);
};
}, []); // Добавляем/удаляем слушатель только один раз
return <p>Window width: {width}</p>;
}
// Монтирование:
// -> эффект выполнен (слушатель добавлен)
// -> перерендеры: слушатель остается
// Размонтирование:
// -> cleanup выполнен (слушатель удален)
Пример 3: Множественные зависимости
function ComplexEffect() {
const [data, setData] = useState(null);
const [filter, setFilter] = useState('');
const [sort, setSort] = useState('asc');
useEffect(() => {
// Выполняется когда filter ИЛИ sort изменились
const url = `/api/data?filter=${filter}&sort=${sort}`;
fetch(url)
.then(res => res.json())
.then(data => setData(data));
}, [filter, sort]); // Зависимости
return (
<div>
<input
value={filter}
onChange={(e) => setFilter(e.target.value)}
/>
<select value={sort} onChange={(e) => setSort(e.target.value)}>
<option value="asc">Ascending</option>
<option value="desc">Descending</option>
</select>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
Таблица сценариев
| Второй аргумент | Когда срабатывает | Примеры использования |
|---|---|---|
| Отсутствует | При каждом рендере | Редко используется (может быть неэффективно) |
| [] | Один раз при монтировании | Загрузка данных, инициализация |
| [dep] | При монтировании и когда dep изменился | Отслеживание изменений параметров |
| [dep1, dep2] | При монтировании и когда dep1 или dep2 изменились | Зависит от нескольких переменных |
Правила использования зависимостей
✅ ПРАВИЛЬНО:
// 1. Включай все переменные из scope которые используются
useEffect(() => {
console.log(count); // используешь count
}, [count]); // count в зависимостях
// 2. Используй пустой массив если не нужны перезапуски
useEffect(() => {
// Инициализирующая логика
}, []); // Один раз
// 3. Если нужна стабильная ссылка на объект, используй useMemo
const config = useMemo(() => ({ token }), [token]);
useEffect(() => {
fetch('/api', config);
}, [config]);
❌ НЕПРАВИЛЬНО:
// 1. Забыл включить зависимость
const [count, setCount] = useState(0);
useEffect(() => {
console.log(count); // используешь count
}); // Забыл [count] -> эффект при каждом рендере
// 2. Объект в зависимостях
const config = { /* ... */ };
useEffect(() => {
// ...
}, [config]); // config новый каждый раз -> бесконечный цикл
// 3. Функция в зависимостях
const handleClick = () => { /* ... */ };
useEffect(() => {
button.addEventListener('click', handleClick);
return () => button.removeEventListener('click', handleClick);
}, [handleClick]); // handleClick новая каждый раз
Инструменты для проверки
// ESLint hook для проверки зависимостей
// npm install --save-dev eslint-plugin-react-hooks
// .eslintrc.json
{
"extends": ["react-app"],
"plugins": ["react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn" // Проверяет зависимости
}
}
Заключение
Второй аргумент useEffect (dependency array) критически важен для:
- Контроля когда эффект запускается
- Избежания бесконечных циклов
- Оптимизации производительности
Помни:
- Нет аргумента = при каждом рендере (редко нужно)
- [] = только при монтировании (для инициализации)
- [deps] = при монтировании и изменении deps (самый частый случай)