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

Как работает UseQuery в RTK Query?

2.0 Middle🔥 182 комментариев
#JavaScript Core

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

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

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

UseQuery в RTK Query

RTK Query (Redux Toolkit Query) - это мощный инструмент для управления кешированием данных и синхронизацией состояния с сервером. useQuery - это основной хук для получения данных.

1. Что такое RTK Query

RTK Query - это встроенный инструмент Redux Toolkit, который предоставляет набор возможностей для упрощения загрузки, кеширования и синхронизации данных с сервером. Это альтернатива React Query с лучшей интеграцией с Redux.

// Установка
// npm install @reduxjs/toolkit react-redux

// Импорты
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

2. Создание API слайса

Первый шаг - определить API с помощью createApi:

// api.ts
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

interface User {
  id: number;
  name: string;
  email: string;
}

export const userApi = createApi({
  reducerPath: 'userApi',
  baseQuery: fetchBaseQuery({
    baseUrl: 'https://api.example.com/api/v1'
  }),
  endpoints: (builder) => ({
    getUser: builder.query<User, number>({
      query: (userId) => `/users/${userId}`
    }),
    getUsers: builder.query<User[], void>({
      query: () => '/users'
    }),
    updateUser: builder.mutation<User, { id: number; data: Partial<User> }>({
      query: ({ id, data }) => ({
        url: `/users/${id}`,
        method: 'PUT',
        body: data
      })
    })
  })
});

export const { useGetUserQuery, useGetUsersQuery, useUpdateUserMutation } = userApi;

3. Базовое использование useQuery

useQuery автоматически управляет состоянием загрузки, ошибок и кеша:

import { useGetUserQuery } from './api';

function UserProfile({ userId }: { userId: number }) {
  // useQuery автоматически выполняет запрос
  const { data, isLoading, error } = useGetUserQuery(userId);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>{data?.name}</h1>
      <p>{data?.email}</p>
    </div>
  );
}

4. Как работает useQuery - внутреннее устройство

Когда вы используете useQuery, происходит следующее:

Шаг 1: Инициализация запроса

  • useQuery генерирует уникальный ключ кеша на основе endpoint и параметров
  • Проверяет, есть ли уже данные в кеше

Шаг 2: Проверка кеша

// RTK Query проверяет кеш
const cacheKey = 'getUser(5)'; // Построено из endpoint и параметров
const cachedData = cache.get(cacheKey);

if (cachedData && !isStale(cachedData)) {
  return cachedData; // Возвращает кешированные данные
}

Шаг 3: Выполнение запроса

  • Если данные отсутствуют или устарели, RTK Query делает HTTP запрос
  • Обновляет состояние (isLoading = true)

Шаг 4: Обновление состояния

  • После ответа данные сохраняются в кеш
  • Компонент перерендеривается с новыми данными

5. Управление кешем

RTK Query имеет встроенное управление кешем с опциями:

function UsersList() {
  // Опции управления кешем
  const { data, isLoading, isFetching } = useGetUsersQuery(undefined, {
    // Как долго данные считаются свежими (в секундах)
    pollingInterval: 0, // 0 = отключен
    
    // Переполучить данные при изменении фокуса окна
    refetchOnFocus: true,
    
    // Переполучить данные при переподключении к сети
    refetchOnReconnect: true,
    
    // Переполучить данные при монтировании компонента
    refetchOnMountOrArgChange: 60, // 60 секунд
    
    // Пропустить запрос
    skip: false
  });

  return (
    <div>
      {isLoading && <p>Loading...</p>}
      {isFetching && <p>Refreshing...</p>}
      {data?.map((user) => <div key={user.id}>{user.name}</div>)}
    </div>
  );
}

6. Полное жизненное действие useQuery

function CompleteExample() {
  const { 
    data,           // Текущие данные из кеша
    isLoading,      // true при первом запросе
    isFetching,     // true когда запрос в процессе
    isSuccess,      // true если запрос успешно завершен
    isError,        // true если произошла ошибка
    error,          // Объект ошибки
    refetch,        // Функция для ручного переполучения
    fulfilledTimeStamp // Временная метка последнего успешного запроса
  } = useGetUsersQuery(undefined, {
    pollingInterval: 30000, // Переполучать каждые 30 секунд
    refetchOnFocus: true,
    refetchOnReconnect: true,
    refetchOnMountOrArgChange: true
  });

  const handleRefresh = () => {
    refetch(); // Принудительно переполучить данные
  };

  return (
    <div>
      {isLoading && <p>First load...</p>}
      {isFetching && !isLoading && <p>Updating...</p>}
      {isSuccess && (
        <>
          <button onClick={handleRefresh}>Refresh</button>
          <ul>
            {data?.map((user) => (
              <li key={user.id}>{user.name}</li>
            ))}
          </ul>
          <small>Last updated: {new Date(fulfilledTimeStamp!).toLocaleTimeString()}</small>
        </>
      )}
      {isError && <p>Error: {(error as any).message}</p>}
    </div>
  );
}

7. Условный запрос с skip

Иногда нужно выполнить запрос только при определенных условиях:

function ConditionalQuery({ userId }: { userId?: number }) {
  // Запрос выполнится только если userId определен
  const { data, isLoading } = useGetUserQuery(userId!, {
    skip: !userId // Пропустить запрос если userId undefined
  });

  if (!userId) return <p>Select a user</p>;
  if (isLoading) return <p>Loading...</p>;

  return <div>{data?.name}</div>;
}

8. Обработка ошибок

function UserWithErrorHandling({ userId }: { userId: number }) {
  const { data, isLoading, error } = useGetUserQuery(userId);

  if (isLoading) return <div>Loading...</div>;

  if (error) {
    const errorMessage = 'status' in error
      ? `Error ${error.status}: ${JSON.stringify(error.data)}`
      : error.message;

    return <div className="error">Failed to load user: {errorMessage}</div>;
  }

  return <div>{data?.name}</div>;
}

9. Комбинирование нескольких запросов

function UserWithPosts({ userId }: { userId: number }) {
  const { data: user, isLoading: userLoading } = useGetUserQuery(userId);
  const { data: posts, isLoading: postsLoading } = useGetUserPostsQuery(userId);

  const isLoading = userLoading || postsLoading;

  if (isLoading) return <div>Loading...</div>;

  return (
    <div>
      <h1>{user?.name}</h1>
      <ul>
        {posts?.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

10. Интеграция с store

// store.ts
import { configureStore } from '@reduxjs/toolkit';
import { userApi } from './api';

export const store = configureStore({
  reducer: {
    [userApi.reducerPath]: userApi.reducer
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(userApi.middleware)
});

// App.tsx
import { Provider } from 'react-redux';

function App() {
  return (
    <Provider store={store}>
      <YourComponent />
    </Provider>
  );
}

Ключевые преимущества RTK Query

  1. Автоматический кеш - встроенное управление кешем
  2. Состояния загрузки - isLoading, isFetching, isSuccess, isError
  3. Переполучение данных - pollingInterval, refetchOnFocus, refetchOnReconnect
  4. Типизация - полная типизация с TypeScript
  5. Нормализация - встроенная нормализация кеша
  6. Optimistic Updates - обновление UI перед получением ответа сервера
  7. Синхронизация - автоматическая синхронизация данных между вкладками

Когда использовать RTK Query

  • Вы уже используете Redux
  • Нужна сложная логика кеширования
  • Требуется TypeScript поддержка
  • Нужна интеграция с Redux DevTools

Альтернативы

  • React Query (TanStack Query) - более гибкий, без зависимости от Redux
  • SWR - простой и лёгкий вариант
  • Axios с простым состоянием - для небольших проектов