← Назад к вопросам
Как работать с асинхронностью в React?
2.0 Middle🔥 231 комментариев
#React#Архитектура и паттерны
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Асинхронность в React
Асинхронные операции (запросы к API, таймеры, работа с файлами) часто нужны в React компонентах. Главная проблема - управление lifecycle'ом компонента и предотвращение утечек памяти.
1. useEffect для асинхронных операций
Утечка памяти происходит если компонент размонтировался, а запрос ещё идет:
import { useEffect, useState } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let isMounted = true; // Флаг для отслеживания
const fetchUser = async () => {
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
if (isMounted) { // Проверяем что компонент ещё в DOM
setUser(data);
}
} catch (err) {
if (isMounted) {
setError(err.message);
}
} finally {
if (isMounted) {
setLoading(false);
}
}
};
fetchUser();
// Cleanup функция
return () => {
isMounted = false; // Отмечаем что компонент размонтировался
};
}, [userId]); // Пересчитываем при изменении userId
if (loading) return <div>Загрузка...</div>;
if (error) return <div>Ошибка: {error}</div>;
return <div>{user.name}</div>;
}
2. Использование AbortController
Современный способ отмены запросов:
function DataFetcher() {
const [data, setData] = useState(null);
useEffect(() => {
const controller = new AbortController();
const fetchData = async () => {
try {
const response = await fetch('/api/data', {
signal: controller.signal // Передаем сигнал отмены
});
const result = await response.json();
setData(result);
} catch (err) {
if (err.name === 'AbortError') {
console.log('Запрос был отменён');
} else {
console.error('Ошибка:', err);
}
}
};
fetchData();
return () => {
controller.abort(); // Отменяем запрос при размонтировании
};
}, []);
return <div>{data ? JSON.stringify(data) : 'Нет данных'}</div>;
}
3. Асинхронные функции с async/await
function PostsList() {
const [posts, setPosts] = useState([]);
useEffect(() => {
const loadPosts = async () => {
try {
// Ждём ответ от API
const response = await fetch('/api/posts');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
setPosts(data);
} catch (error) {
console.error('Ошибка загрузки постов:', error);
}
};
loadPosts();
}, []);
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
4. Обработка нескольких асинхронных операций
function Dashboard() {
const [stats, setStats] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const controller = new AbortController();
const fetchData = async () => {
try {
// Promise.all для параллельных запросов
const [usersRes, postsRes, commentsRes] = await Promise.all([
fetch('/api/users', { signal: controller.signal }),
fetch('/api/posts', { signal: controller.signal }),
fetch('/api/comments', { signal: controller.signal }),
]);
const [users, posts, comments] = await Promise.all([
usersRes.json(),
postsRes.json(),
commentsRes.json(),
]);
setStats({ users, posts, comments });
} catch (error) {
if (error.name !== 'AbortError') {
console.error('Ошибка:', error);
}
} finally {
setLoading(false);
}
};
fetchData();
return () => controller.abort();
}, []);
return loading ? <div>Загрузка...</div> : <div>{JSON.stringify(stats)}</div>;
}
5. Дебаунсирование асинхронных запросов
function SearchUsers({ query }) {
const [results, setResults] = useState([]);
useEffect(() => {
// Создаём таймер для дебаунса
const timer = setTimeout(async () => {
if (query.length === 0) {
setResults([]);
return;
}
try {
const response = await fetch(`/api/search?q=${query}`);
const data = await response.json();
setResults(data);
} catch (error) {
console.error('Ошибка поиска:', error);
}
}, 500); // Ждём 500ms после последнего изменения
return () => clearTimeout(timer); // Отменяем старый таймер
}, [query]);
return <ul>{results.map(r => <li key={r.id}>{r.name}</li>)}</ul>;
}
6. Кастомный Hook для загрузки данных
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
const fetchData = async () => {
try {
const response = await fetch(url, { signal: controller.signal });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json();
setData(result);
} catch (err) {
if (err.name !== 'AbortError') setError(err);
} finally {
setLoading(false);
}
};
fetchData();
return () => controller.abort();
}, [url]);
return { data, loading, error };
}
// Использование
function App() {
const { data, loading, error } = useFetch('/api/users');
if (loading) return <div>Загрузка...</div>;
if (error) return <div>Ошибка: {error.message}</div>;
return <div>{JSON.stringify(data)}</div>;
}
Основные правила
- НИКОГДА не передавай async прямо в useEffect
- Всегда проверяй isMounted или используй AbortController
- Очищай ресурсы в cleanup функции
- Правильно настроившь dependency array (пустой [] - один раз)
- Для очень сложной логики рассмотри React Query или SWR