Что такое useEffect в React и как правильно его использовать?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое useEffect в React и как правильно его использовать?
useEffect — это React Hook, который позволяет выполнять побочные эффекты (side effects) в функциональных компонентах. Побочный эффект — это любая операция, которая влияет на мир вне компонента: запросы к API, подписки, изменение DOM и т.д.
Синтаксис
useEffect(() => {
// Код побочного эффекта выполняется после каждого рендера
console.log('Компонент отрисован');
}, [dependencies]);
useEffect принимает два аргумента:
- Функция эффекта — код, который нужно выполнить
- Массив зависимостей — опционально, контролирует когда запускается эффект
Три варианта использования
1. Без массива зависимостей
Эффект запускается после каждого рендера:
function Example() {
useEffect(() => {
console.log('Рендер произошёл');
});
return <h1>Привет</h1>;
}
// Выполнится после каждого рендера
Это опасно и обычно вызывает бесконечные циклы:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // Бесконечный цикл!
}); // Нет зависимостей
return <div>{count}</div>;
}
2. С пустым массивом зависимостей
Эффект запускается только один раз при монтировании компонента:
function FetchUser() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch('/api/user')
.then(res => res.json())
.then(data => setUser(data));
}, []); // Пустой массив — только при монтировании
return <div>{user?.name}</div>;
}
3. С массивом зависимостей
Эффект запускается только когда меняются зависимости:
function SearchUsers({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
if (query.length === 0) return;
fetch(`/api/search?q=${query}`)
.then(res => res.json())
.then(data => setResults(data));
}, [query]); // Запускается при изменении query
return <ul>{results.map(r => <li>{r.name}</li>)}</ul>;
}
Очистка (Cleanup)
Если эффект создаёт ресурсы (подписки, таймеры), нужна функция очистки:
function ChatWindow({ userId }) {
useEffect(() => {
// Подписываемся на сообщения
const unsubscribe = subscribeToMessages(userId, (msg) => {
console.log(msg);
});
// Возвращаем функцию очистки
return () => {
unsubscribe(); // Отписываемся при размонтировании или перед новым эффектом
};
}, [userId]);
return <div>Chat</div>;
}
Очистка выполняется:
- Перед новым выполнением эффекта (если зависимости изменились)
- Перед размонтированием компонента
Проблемы и как их избежать
1. Бесконечные циклы
// ПЛОХО: Эффект создаёт новый объект, что вызывает новый эффект
useEffect(() => {
setData({ items: [] });
}, [data]); // data меняется в эффекте!
// ХОРОШО: Пустой массив — эффект только один раз
useEffect(() => {
setData({ items: [] });
}, []);
2. Закрытые переменные (stale closures)
// ПЛОХО: count всегда 0 в таймере
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
console.log(count); // Всегда 0
}, 1000);
return () => clearInterval(timer);
}, []); // count не в зависимостях
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
// ХОРОШО: count в зависимостях
useEffect(() => {
const timer = setInterval(() => {
console.log(count); // Правильное значение
}, 1000);
return () => clearInterval(timer);
}, [count]); // count добавлен в зависимости
3. Утечки памяти
// ПЛОХО: Таймер не очищается
useEffect(() => {
setInterval(() => {
console.log('tick');
}, 1000);
}, []);
// ХОРОШО: Таймер очищается
useEffect(() => {
const id = setInterval(() => {
console.log('tick');
}, 1000);
return () => clearInterval(id);
}, []);
Правила использования useEffect
- Всегда включай все зависимости в массив (используй eslint-plugin-react-hooks)
- Разделяй эффекты — каждый эффект должен отвечать за одно
- Используй useCallback или useMemo если эффект зависит от функций/объектов:
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
// ПЛОХО: fetchUser пересоздаётся каждый рендер
useEffect(() => {
const fetchUser = async () => {
const res = await fetch(`/api/users/${userId}`);
setUser(await res.json());
};
fetchUser();
}, [userId]); // Но функция не в зависимостях!
// ХОРОШО: Используем useCallback
const fetchUser = useCallback(async () => {
const res = await fetch(`/api/users/${userId}`);
setUser(await res.json());
}, [userId]);
useEffect(() => {
fetchUser();
}, [fetchUser]);
}
Практические примеры
Запрос данных:
function BlogPost({ postId }) {
const [post, setPost] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(`/api/posts/${postId}`)
.then(res => {
if (!res.ok) throw new Error('Failed');
return res.json();
})
.then(data => setPost(data))
.catch(err => setError(err.message))
.finally(() => setLoading(false));
}, [postId]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return <article>{post?.content}</article>;
}
Синхронизация с внешним хранилищем:
function ThemeProvider() {
const [theme, setTheme] = useState('light');
useEffect(() => {
// Сохраняем в localStorage при изменении
localStorage.setItem('theme', theme);
document.documentElement.className = theme;
}, [theme]);
// Загружаем из localStorage при монтировании
useEffect(() => {
const saved = localStorage.getItem('theme');
if (saved) setTheme(saved);
}, []);
return <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>Toggle</button>;
}
Вывод
useEffect — это мощный инструмент для управления побочными эффектами в React. Главное правило: всегда указывай правильный массив зависимостей, правильно очищай ресурсы и разделяй эффекты по логике. Правильное использование useEffect — это основа построения надёжных React приложений.