← Назад к вопросам

Как происходит кэширование данных в React Query?

2.0 Middle🔥 241 комментариев
#React#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Кэширование данных в React Query (TanStack Query)

React Query (теперь TanStack Query) - это библиотека для управления состоянием серверных данных с умным кэшированием. Давайте разберемся как оно работает.

Основная концепция: Query Keys и Cache

React Query хранит данные в памяти по ключам запроса:

import { useQuery } from '@tanstack/react-query';

function useUser(userId) {
  return useQuery({
    queryKey: ['users', userId],  // Уникальный ключ
    queryFn: async () => {
      const response = await fetch(`/api/v1/users/${userId}`);
      return response.json();
    }
  });
}

// Если вызвать дважды с одинаковым userId
// Второй вызов вернет кэшированные данные без запроса
const user1 = useUser(123);  // API запрос
const user2 = useUser(123);  // Из кэша

Жизненный цикл кэша

const { status, data } = useQuery({
  queryKey: ['posts'],
  queryFn: fetchPosts,
  staleTime: 5 * 60 * 1000,      // 5 минут - данные "свежие"
  gcTime: 10 * 60 * 1000          // 10 минут - данные в памяти
});

// Состояния:
// 1. fresh (свежие) - использу из кэша БЕЗ перезапроса
// 2. stale (устаревшие) - используй из кэша, но запроси обновление
// 3. invalid - нет данных, нужен запрос
// 4. gc (garbage collected) - выброшено из памяти

Визуальная шкала времени

Время ->  0мин ----5мин (staleTime)--- 10мин (gcTime)---- 15мин
           |         |                   |                    |
Статус    fresh     stale              evicted            gone
Кэш       есть      есть               нет                нет
Zapros    нет       фоновый            да                 да

Стратегии кэширования

1. Кэш и синхронизация в фоне

// Данные из кэша сразу, но обновляется в фоне
function UserProfile({ userId }) {
  const { data, isLoading, isRefetching } = useQuery({
    queryKey: ['users', userId],
    queryFn: fetchUser,
    staleTime: 1 * 60 * 1000,  // 1 минута
    refetchOnWindowFocus: true   // Обновить при фокусе окна
  });

  return (
    <div>
      {data && <h1>{data.name}</h1>}
      {isRefetching && <span>Обновляется...</span>}
    </div>
  );
}

2. Долгоживущий кэш

// Данные кэшируются надолго, редко обновляются
function CountriesList() {
  return useQuery({
    queryKey: ['countries'],
    queryFn: fetchCountries,
    staleTime: 24 * 60 * 60 * 1000,  // 24 часа
    gcTime: 48 * 60 * 60 * 1000       // 48 часов
  });
}

3. Всегда свежие данные

// Запрашиваем сервер при каждом использовании
function RealtimeNotifications() {
  return useQuery({
    queryKey: ['notifications'],
    queryFn: fetchNotifications,
    staleTime: 0,              // Сразу считаются устаревшими
    refetchInterval: 3000      // Запрос каждые 3 секунды
  });
}

Инвалидация кэша (Invalidation)

Чтобы заставить перезапрос:

function CreatePost() {
  const queryClient = useQueryClient();
  const createMutation = useMutation({
    mutationFn: createPost,
    onSuccess: () => {
      // После создания - инвалидируем кэш постов
      queryClient.invalidateQueries({
        queryKey: ['posts']
      });
    }
  });

  return (
    <button onClick={() => createMutation.mutate({ title: 'New' })}>
      Создать пост
    </button>
  );
}

Wildcards в Query Keys

const queryClient = useQueryClient();

// Инвалидировать все посты любого пользователя
await queryClient.invalidateQueries({
  queryKey: ['users']  // Все запросы начинающиеся с 'users'
});

// Инвалидировать посты конкретного пользователя
await queryClient.invalidateQueries({
  queryKey: ['users', 123, 'posts']
});

// Более точно с exact
await queryClient.invalidateQueries({
  queryKey: ['users', 123],
  exact: true  // Только точное совпадение
});

Оптимистичные обновления

function useUpdateUser() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: updateUserOnServer,
    onMutate: async (newData) => {
      // Отмени текущие запросы
      await queryClient.cancelQueries({ queryKey: ['users'] });
      
      // Сохрани старые данные
      const previousUser = queryClient.getQueryData(['users', newData.id]);
      
      // Обнови кэш оптимистично
      queryClient.setQueryData(['users', newData.id], newData);
      
      return { previousUser };  // Возврат контекста
    },
    onError: (err, newData, context) => {
      // При ошибке откати
      if (context?.previousUser) {
        queryClient.setQueryData(
          ['users', newData.id],
          context.previousUser
        );
      }
    },
    onSuccess: () => {
      // После успеха инвалидируй кэш
      queryClient.invalidateQueries({ queryKey: ['users'] });
    }
  });
}

Отключение кэширования

// Каждый раз запрашиваем с сервера
function AlwaysFreshData() {
  return useQuery({
    queryKey: ['data'],
    queryFn: fetchData,
    staleTime: 0,              // Никогда не кэшируй
    gcTime: 0,                 // Не храни
    enabled: true              // Автозапрос
  });
}

Синхронизация между вкладками

// React Query автоматически синхронизирует кэш
// когда переключаешься между вкладками
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      refetchOnWindowFocus: true  // Обновить при фокусе
    }
  }
});

// Отработает когда вернешь фокус на вкладку

Отладка кэша

// Установи React Query DevTools
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

function App() {
  const queryClient = new QueryClient();
  return (
    <QueryClientProvider client={queryClient}>
      <ReactQueryDevtools />
      {/* app */}
    </QueryClientProvider>
  );
}

// Открой DevTools -> React Query
// Увидишь все ключи, их статус, размер кэша, времена

Практический пример: Список пользователей

function UsersList() {
  const queryClient = useQueryClient();
  const [page, setPage] = useState(1);
  
  const { data, isLoading } = useQuery({
    queryKey: ['users', { page }],
    queryFn: () => fetchUsers(page),
    staleTime: 5 * 60 * 1000,
    keepPreviousData: true  // Показать старые данные при load
  });
  
  const deleteMutation = useMutation({
    mutationFn: deleteUser,
    onSuccess: () => {
      // Инвалидируй список
      queryClient.invalidateQueries({ queryKey: ['users'] });
    }
  });

  return (
    <div>
      {isLoading && <Spinner />}
      {data?.users.map(user => (
        <div key={user.id}>
          {user.name}
          <button onClick={() => deleteMutation.mutate(user.id)}>
            Удалить
          </button>
        </div>
      ))}
    </div>
  );
}

Основные параметры кэша

  • queryKey: Уникальный ключ для кэша
  • staleTime: Как долго данные считаются свежими (0 = сразу устаревают)
  • gcTime: Как долго неиспользуемые данные в памяти (ранее cacheTime)
  • refetchOnWindowFocus: Обновить при фокусе окна
  • refetchInterval: Интервал фонового обновления
  • enabled: Включить/выключить автоматический запрос
  • keepPreviousData: Показать старые данные во время загрузки

Выводы

React Query кэширует по ключам, умно управляет памятью, и предоставляет инструменты для инвалидации и синхронизации. Это экономит запросы и ускоряет приложение.