Зачем вам нужно управление кэшем?
Итак, представьте, что ваш React-приложение — это ресторан. Если ваш сервер — это кухня, то кэш — это холодильник, где хранятся готовые блюда, чтобы их быстро раздать без повторного приготовления (то бишь запроса к серверу). Управление кэшем позволяет:
- Снизить нагрузку на сервер (зачем запрашивать одно и то же по 100 раз?).
- Ускорить отображение данных для пользователя (вот вам запрошенный ранее бургер, приятного аппетита!).
- Улучшить пользовательский опыт (никто не любит ждать).
React Query автоматически кэширует данные запросов, но иногда у нас есть специфические требования. Например, заставить данные обновляться быстрее или синхронизироваться с другими изменениями в приложении.
Как React Query управляет кэшем?
React Query — это своего рода умный менеджер кэша. При каждом запросе библиотека сохраняет данные в кэш и присваивает им временную метку. Эти данные можно считать "свежими", пока не истечёт определённое время. После этого библиотеки автоматически помечает их как "устаревшие", а в некоторых случаях может повторно запросить данные с сервера.
Основные параметры, которые помогают контролировать кэш:
staleTime— время, в течение которого данные считаются свежими (по умолчанию 0).cacheTime— время, в течение которого данные остаются в кэше, даже если больше не используются (по умолчанию 5 минут).
Обновление данных в кэше
Зачастую вам нужно вручную обновить кэш для синхронизации со статусом вашего приложения. React Query предоставляет удобные функции для этого:
Использование QueryClient.setQueryData
Метод setQueryData позволяет обновить данные определённого запроса в кэше без взаимодействия с сервером. Это называется оптимистичным обновлением — вы предполагаете, что результат операции успешен, и обновляете интерфейс заранее. Если сервер вернёт ошибку, данные можно откатить.
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
// Пример API-функции
const fetchTodo = async (id: number) => {
const response = await fetch(`/api/todos/${id}`);
if (!response.ok) throw new Error('Ошибка при получении задачи');
return response.json();
};
const updateTodo = async (todo: { id: number; title: string }) => {
const response = await fetch(`/api/todos/${todo.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(todo),
});
if (!response.ok) throw new Error('Ошибка при обновлении задачи');
return response.json();
};
const TodoComponent = ({ id }: { id: number }) => {
const queryClient = useQueryClient();
// Получение данных Todo
const { data: todo } = useQuery({
queryKey: ['todo', id],
queryFn: () => fetchTodo(id),
});
// Мутация для обновления Todo
const mutation = useMutation({
mutationFn: updateTodo,
onMutate: async (updatedTodo) => {
// Отменяем любые исходящие запросы для этого Todo
await queryClient.cancelQueries({ queryKey: ['todo', id] });
// Сохраняем прежние данные для возможного отката
const previousTodo = queryClient.getQueryData(['todo', id]);
// Обновляем кэш с новыми данными (оптимистично)
queryClient.setQueryData(['todo', id], updatedTodo);
// Возвращаем прежние данные для возможного отката
return { previousTodo };
},
onError: (error, updatedTodo, context) => {
// Откат изменений в случае ошибки
if (context?.previousTodo) {
queryClient.setQueryData(['todo', id], context.previousTodo);
}
},
onSettled: () => {
// Повторно загружаем данные с сервера после завершения мутации
queryClient.invalidateQueries({ queryKey: ['todo', id] });
},
});
return (
<div>
<h3>{todo?.title}</h3>
<button
onClick={() =>
mutation.mutate({ id, title: `${todo?.title} (обновлено!)` })
}
>
Обновить
</button>
</div>
);
};
export default TodoComponent;
Использование invalidateQueries
Если вы хотите просто сказать React Query: "Эй, эти данные устарели, обнови-ка их!", используйте invalidateQueries. Этот метод заставляет библиотеку повторно выполнить запрос для указанных ключей.
const queryClient = useQueryClient();
queryClient.invalidateQueries({ queryKey: ['todo', id] });
Использование refetchQueries
Метод refetchQueries похож на invalidateQueries, но в данном случае вы явно повторно запрашиваете данные (рефетчинг).
queryClient.refetchQueries({ queryKey: ['todo'] });
staleTime и cacheTime: тонкая настройка
staleTime и cacheTime — это два ключевых параметра, которые позволяют управлять временем жизни данных в кэше.
staleTime(в миллисекундах) определяет, как долго данные считаются "свежими". В течение этого времени React Query не будет повторно запрашивать их при повторном рендере компонента.cacheTime(в миллисекундах) определяет, как долго данные остаются в памяти (даже после того, как компонент размонтирован). После истеченияcacheTimeданные удаляются из кэша.
Пример настройки:
const { data } = useQuery({
queryKey: ['todo', id],
queryFn: () => fetchTodo(id),
staleTime: 1000 * 60, // 1 минута
cacheTime: 1000 * 60 * 5, // 5 минут
});
Примеры использования в реальном мире
Сценарий 1: локальное обновление данных после мутации
Представьте список задач (To-Do) и случай, когда пользователь добавляет новую задачу. Вместо повторного запроса списка задач вы можете оптимистически обновить кэш, добавив данные новой задачи.
Сценарий 2: автоматическое обновление при фокусе на вкладке
С помощью параметра refetchOnWindowFocus вы можете настроить автоматическое обновление данных при возврате пользователя на вкладку браузера.
const { data } = useQuery({
queryKey: ['todos'],
queryFn: fetchTodos,
refetchOnWindowFocus: true,
});
Сценарий 3: отложенное удаление данных
Если данные редко меняются, вы можете увеличить cacheTime, чтобы кэш оставался в памяти дольше, и избежать лишних запросов.
Типичные ошибки
Иногда управление кэшем может вызывать неожиданные проблемы. Например, если вы забыли вызвать invalidateQueries после выполнения мутации, интерфейс пользователя может остаться с устаревшими данными. Либо, наоборот, высокие значения staleTime могут привести к тому, что новые данные с сервера останутся невидимыми для пользователя.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ