Библиотеки загрузки данных
Хук useFetch из урока о собственных хуках решает простую задачу. В настоящем приложении требований больше: не загружать одну и ту же статью дважды, обновлять данные при возвращении на вкладку, повторять неудачный запрос. Писать всё это вручную долго, поэтому используют библиотеки загрузки данных. Они хранят ответы в кэше — памяти, откуда данные отдаются сразу, без нового запроса.
Популярные библиотеки
| Библиотека | Особенность |
|---|---|
| TanStack Query | Самая распространённая, работает с любым способом запроса |
| SWR | Похожая идея, более компактный API |
| RTK Query | Встроена в Redux Toolkit |
Дальше — пример на TanStack Query (версия 5).
Установка и подключение
npm install @tanstack/react-queryПриложение оборачивают в провайдер с клиентом запросов — в src/main.jsx:
// src/main.jsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
// импорты StrictMode, createRoot, BrowserRouter и App — как раньше
const queryClient = new QueryClient();
createRoot(document.getElementById('root')).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<App />
</BrowserRouter>
</QueryClientProvider>
</StrictMode>,
);QueryClient хранит кэш всех запросов приложения.
Запрос с useQuery
Перепишем список статей — src/components/PostList.jsx:
// src/components/PostList.jsx
import { useQuery } from '@tanstack/react-query';
import { Link } from 'react-router';
async function fetchPosts() {
const response = await fetch('https://jsonplaceholder.typicode.com/posts');
if (!response.ok) throw new Error(`Ошибка ${response.status}`);
return response.json();
}
export default function PostList() {
const { data, isPending, error } = useQuery({
queryKey: ['posts'],
queryFn: fetchPosts,
});
if (isPending) return <p>Загрузка…</p>;
if (error) return <p>Ошибка: {error.message}</p>;
return (
<ul>
{data.map((post) => (
<li key={post.id}>
<Link to={`/posts/${post.id}`}>{post.title}</Link>
</li>
))}
</ul>
);
}queryFn — функция, которая загружает данные и бросает ошибку при неудаче. queryKey — ключ, под которым ответ лежит в кэше. Откройте статью и вернитесь на главную: список появится сразу, без «Загрузка…», — он взят из кэша и тихо обновлён в фоне.
Ключ с параметром
Если запрос зависит от параметра, параметр кладут в ключ:
// src/pages/PostPage.jsx
const { id } = useParams();
const { data: post, isPending } = useQuery({
queryKey: ['posts', id],
queryFn: () =>
fetch(`https://jsonplaceholder.typicode.com/posts/${id}`).then((r) => r.json()),
});Для каждой статьи будет своя запись в кэше: ['posts', '1'], ['posts', '2']. Повторный переход на уже открытую статью покажет её мгновенно.
Что ещё умеет библиотека
| Возможность | Как называется |
|---|---|
| Отправка данных на сервер | useMutation |
| Сброс кэша после изменения | queryClient.invalidateQueries |
| Повтор неудачного запроса | Включён по умолчанию, 3 попытки |
Частая ошибка
Если параметр запроса не попал в ключ, библиотека считает все запросы одним и показывает чужие данные:
// Неправильно: у всех статей один ключ.
useQuery({ queryKey: ['post'], queryFn: () => fetchPost(id) });
// Правильно:
useQuery({ queryKey: ['post', id], queryFn: () => fetchPost(id) });Всё, от чего зависит queryFn, должно быть в queryKey.