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

Как работают слайсы в RTK Query?

2.0 Middle🔥 181 комментариев
#State Management#Архитектура и паттерны

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

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

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

Слайсы в RTK Query: управление состоянием для API

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

Что такое RTK Query?

RTK Query автоматически генерирует Redux actions и reducers для работы с API. Вместо ручного написания всех этих экшенов, ты определяешь API endpoints, а RTK Query создает весь boilerplate.

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

interface Question {
  id: string;
  title: string;
  text: string;
}

export const questionsApi = createApi({
  reducerPath: 'questionsApi',
  baseQuery: fetchBaseQuery({ baseUrl: 'https://api.prepbro.ru' }),
  endpoints: (builder) => ({
    // GET endpoint - автоматически кэшируется
    getQuestions: builder.query<Question[], string>({
      query: (professionId) => `/api/v1/questions?profession_id=${professionId}`,
    }),
    
    // POST endpoint - изменяет состояние
    createComment: builder.mutation<{ ok: boolean }, { questionId: string; content: string }>({
      query: (body) => ({
        url: '/api/v1/comments',
        method: 'POST',
        body,
      }),
      // Инвалидировать кэш после успешного запроса
      invalidatesTags: ['Questions'],
    }),
  }),
});

export const { useGetQuestionsQuery, useCreateCommentMutation } = questionsApi;

Интеграция с Redux Store

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

export const store = configureStore({
  reducer: {
    // RTK Query создает свой reducer с специальным именем
    [questionsApi.reducerPath]: questionsApi.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware()
      .concat(questionsApi.middleware), // middleware для обработки запросов
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

Использование в компонентах

// components/QuestionsList.tsx
import { useGetQuestionsQuery, useCreateCommentMutation } from '@/redux/api';

export function QuestionsList({ professionId }: { professionId: string }) {
  // Хук для query endpoint - автоматически загружает данные
  const { data: questions, isLoading, isError, error } = useGetQuestionsQuery(professionId);
  
  // Хук для mutation endpoint - для изменений
  const [createComment, { isLoading: isCreating }] = useCreateCommentMutation();
  
  if (isLoading) return <div>Загрузка вопросов...</div>;
  if (isError) return <div>Ошибка: {error?.toString()}</div>;
  
  const handleComment = async (questionId: string, content: string) => {
    try {
      const result = await createComment({ questionId, content }).unwrap();
      console.log('Комментарий создан:', result);
    } catch (err) {
      console.error('Ошибка при создании комментария:', err);
    }
  };
  
  return (
    <ul>
      {questions?.map((q) => (
        <li key={q.id}>
          <h3>{q.title}</h3>
          <button
            onClick={() => handleComment(q.id, 'Мой ответ')}
            disabled={isCreating}
          >
            {isCreating ? 'Отправка...' : 'Ответить'}
          </button>
        </li>
      ))}
    </ul>
  );
}

Теги и инвалидация кэша

RTK Query использует систему тегов для умного кэширования:

export const questionsApi = createApi({
  // ...
  tagTypes: ['Questions', 'Comments'],
  endpoints: (builder) => ({
    getQuestions: builder.query<Question[], string>({
      query: (professionId) => `/api/v1/questions?profession_id=${professionId}`,
      providesTags: ['Questions'], // этот endpoint "предоставляет" тег Questions
    }),
    
    getComments: builder.query<Comment[], string>({
      query: (questionId) => `/api/v1/questions/${questionId}/comments`,
      providesTags: (result) =>
        result
          ? [
              ...result.map((comment) => ({ type: 'Comments' as const, id: comment.id })),
              'Comments', // также предоставляет общий тег
            ]
          : ['Comments'],
    }),
    
    createComment: builder.mutation<Comment, CreateCommentInput>({
      query: (body) => ({
        url: '/api/v1/comments',
        method: 'POST',
        body,
      }),
      // После успешного создания инвалидировать кэш
      invalidatesTags: ['Comments', 'Questions'],
    }),
  }),
});

Преимущества RTK Query

// 1. Автоматическое кэширование
const { data: q1 } = useGetQuestionsQuery('prof1');
const { data: q2 } = useGetQuestionsQuery('prof1'); // из кэша, без нового запроса

// 2. Отслеживание состояния загрузки
const { isLoading, isSuccess, isError, error } = useGetQuestionsQuery('prof1');

// 3. Переполнение кэша (refetch)
const { refetch } = useGetQuestionsQuery('prof1');
refetch(); // заново загрузить данные

// 4. Оптимистичные обновления
const [createComment] = useCreateCommentMutation();
await createComment({
  questionId: 'id1',
  content: 'text',
}).unwrap();
// После mutation RTK Query автоматически инвалидирует связанные данные

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

function CommentForm({ questionId }: { questionId: string }) {
  const [createComment, { isLoading, error: createError }] = useCreateCommentMutation();
  
  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    try {
      await createComment({
        questionId,
        content: e.currentTarget.comment.value,
      }).unwrap();
      e.currentTarget.reset();
    } catch (err) {
      // FetchBaseQueryError или SerializedError
      console.error('Failed to create comment:', err);
    }
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <textarea name="comment" required />
      {createError && <p className="text-red-500">Ошибка: {JSON.stringify(createError)}</p>}
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Отправка...' : 'Комментировать'}
      </button>
    </form>
  );
}

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

  • Когда есть обычные CRUD операции через REST API
  • Когда нужно кэширование данных
  • Когда хочешь избежать боilerplate Redux кода
  • Когда важна синхронизация данных между компонентами

RTK Query заменяет большую часть работы с Redux для работы с API. Вместо сотни строк кода, ты пишешь несколько endpoints и все работает автоматически.

Как работают слайсы в RTK Query? | PrepBro