Почему кэширование так важно?
Вот вам аналогия. Представьте, что вы живёте в доме, в котором есть большая библиотека. Каждый раз, когда вам нужно перечитать книгу, вместо того чтобы идти в библиотеку, можно просто взять её с полки в вашей комнате. Экономия времени? Да! Так вот, эту комнатную полку можно сравнить с кэшированием данных в приложении.
Кэширование позволяет:
- Уменьшить количество запросов к серверу и сэкономить трафик.
- Ускорить время отклика приложения.
- Повысить стабильность работы, показывая пользователю уже загруженные данные, даже если интернет-соединение пропадёт.
React Query делает кэширование "из коробки", но иногда есть смысл настроить его под конкретные задачи.
Основы кэширования в React Query
React Query автоматически кэширует результаты запросов. Это означает, что если вы отправляете одинаковый запрос повторно, данные возьмутся из кэша, а не с сервера, если их "свежесть" ещё актуальна. Давайте разберем, как это работает.
Кэширование и жизненный цикл данных
Каждый запрос в React Query имеет три состояния:
- Stale (устаревший) — данные, которые потенциально могут быть неактуальными.
- Fresh (свежий) — данные, которые актуальны и берутся из кэша.
- Garbage (удалённый) — данные, которые больше не нужны и удалены из кэша.
Управление временем жизни кэша
React Query предоставляет два параметра, которые управляют жизненным циклом данных:
staleTime— время в миллисекундах, в течение которого данные считаются актуальными. Например, еслиstaleTimeустановлен в 5000 мс, данные из кэша будут использоваться без дополнительного запроса в течение 5 секунд.cacheTime— время в миллисекундах, в течение которого данные остаются в кэше после того, как стали "устаревшими". По умолчанию это 5 минут.
Настройка staleTime и cacheTime
Переходим к практике. Предположим, у нас есть список пользователей, который не меняется часто. Мы можем увеличить staleTime и cacheTime, чтобы уменьшить количество запросов к серверу.
import {useQuery, QueryClient,QueryClientProvider} from '@tanstack/react-query';
// Создаем QueryClient
const queryClient = new QueryClient();
// Компонент с запросом
function UserList() {
const { data, isLoading, isError } = useQuery({
queryKey: ['users'], // Новый формат ключа
queryFn: async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) throw new Error('Ошибка при загрузке');
return response.json();
},
staleTime: 10000, // 10 секунд
cacheTime: 600000, // 10 минут
});
if (isLoading) return <p>Загрузка...</p>;
if (isError) return <p>Ошибка загрузки данных.</p>;
return (
<ul>
{data?.map((user: any) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<UserList />
</QueryClientProvider>
);
}
Пояснение:
staleTime: 10000— если пользователь обновит список в течение 10 секунд, данные вернутся из кэша.cacheTime: 600000— данные сохраняются в кэше 10 минут после того, как истеклоstaleTime.
Такой подход особенно полезен для часто используемых данных, которые не меняются часто, например, списков товаров или профилей пользователей.
Оптимистичное обновление данных
Оптимистичное обновление — это ситуация, когда вы обновляете UI сразу, предполагая, что запрос к серверу выполнится успешно. Это особенно полезно для улучшения пользовательского опыта.
Приведем пример. Давайте добавим возможность удалить пользователя. При этом UI обновится моментально, не дожидаясь ответа от сервера.
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
function UserList() {
const queryClient = useQueryClient();
// Получение списка пользователей
const { data, isLoading } = useQuery({
queryKey: ['users'],
queryFn: async () => {
const response = await fetch('https://jsonplaceholder.typicode.com/users');
if (!response.ok) throw new Error('Ошибка при получении пользователей');
return response.json();
},
});
// Мутация удаления пользователя с оптимистичным обновлением
const mutation = useMutation({
mutationFn: async (id: number) => {
await fetch(`https://jsonplaceholder.typicode.com/users/${id}`, {
method: 'DELETE',
});
},
onMutate: async (id) => {
// Отмена текущих запросов
await queryClient.cancelQueries({ queryKey: ['users'] });
// Сохраняем текущее состояние
const previousUsers = queryClient.getQueryData<any[]>(['users']);
// Оптимистичное обновление кэша
queryClient.setQueryData<any[]>(['users'], (old) =>
old ? old.filter((user) => user.id !== id) : []
);
return { previousUsers };
},
onError: (_err, _id, context) => {
// В случае ошибки откат к предыдущему состоянию
if (context?.previousUsers) {
queryClient.setQueryData(['users'], context.previousUsers);
}
},
onSettled: () => {
// Повторно запрашиваем пользователей после завершения
queryClient.invalidateQueries({ queryKey: ['users'] });
},
});
if (isLoading) return <p>Загрузка...</p>;
return (
<ul>
{data?.map((user: any) => (
<li key={user.id}>
{user.name}{' '}
<button onClick={() => mutation.mutate(user.id)} disabled={mutation.isPending}>
{mutation.isPending ? 'Удаление...' : 'Удалить'}
</button>
</li>
))}
</ul>
);
}
Пояснение:
- Функция
onMutateоптимистично обновляет список пользователей, сразу удаляя из UI того, кого хотят удалить. - Если запрос завершится с ошибкой, мы откатываем изменения с помощью
onError. onSettledиспользуется для инвалидирования запроса и повторного получения данных.
Применение в реальных проектах
Эти техники могут быть полезны в следующих случаях:
- Каталоги и списки: для отображения продуктов или услуг, которые не изменяются каждую секунду.
- Личные кабинеты: данные пользователя, которые редко обновляются, но нужны при каждом входе.
- Оптимизация пользовательского опыта: для операций, требующих подтверждения на сервере (например, лайки, избранное).
Советы по настройке кэширования
- Используйте большее значение
staleTimeдля редко изменяемых данных. - Установите меньшее значение
cacheTimeдля часто обновляемых данных. - Используйте
onMutateдля оптимистичных обновлений и минимизации задержек.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ