Как использовать Redux Toolkit Query (RTK Query) в React — руководство
Что вы узнаете
- Что такое RTK Query и когда его использовать
- Как настроить apiSlice и store шаг за шагом
- Примеры кода и улучшения для реального приложения
- Лучшие практики, тестирование и распространённые ошибки
- Сравнение с альтернативами и чек-листы для ролей
Что такое Redux Toolkit Query
Redux Toolkit Query — это инструмент для запросов данных и кэширования, построенный поверх Redux Toolkit. Он автоматизирует рутинную логику: запросы, кэш, инвалидацию, повторные запросы и управление состоянием загрузки/ошибки. Коротко — делает работу с удалёнными данными более предсказуемой и декларативной.
Определение термина: RTK Query — библиотека для упрощения загрузки и кэширования данных в приложениях на базе Redux.
Важно: RTK Query не обязательно нужен всем проектам. Для простых локальных состояний и редких запросов достаточно fetch или небольших хуков. RTK Query полезен, когда у вас много связанных эндпоинтов, требуется кэш, инвалидация или централизованное управление состоянием API.
Преимущества RTK Query
- Интеграция с Redux — единое хранилище для состояния и кэша API
- Генерация React хуков автоматически по эндпоинтам
- Кэширование, повторные запросы, инвалидация тегов
- Поддержка optimistic updates и polling
- Минимум шаблонного кода
Быстрый старт: установка и запуск
У вас уже может быть проект, созданный через Create React App или Vite. Пример быстрого запуска с CRA:
mkdir React-RTQ
cd React-RTQ
npx create-react-app react-rtq-example
cd react-rtq-example
npm startУстановка зависимостей:
npm install @reduxjs/toolkit react-reduxОпределение apiSlice
Api slice — это место, где вы описываете базовый url и набор эндпоинтов (queries и mutations). Простой пример apiSlice, затем улучшенный вариант с тегами и transformResponse.
Исходный пример (переведён и с исправлением возможной опечатки в reducerPath):
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
export const productsApi = createApi({
reducerPath: "productsApi",
baseQuery: fetchBaseQuery({ baseUrl: "https://dummyjson.com/" }),
endpoints: (builder) => ({
getAllProducts: builder.query({
query: () => "products",
}),
getProduct: builder.query({
query: (product) => `products/search?q=${product}`,
}),
}),
});
export const { useGetAllProductsQuery, useGetProductQuery } = productsApi;Рекомендации:
- reducerPath должен быть уникальным и читабельным (например, “productsApi”).
- Добавляйте providesTags/invalidatesTags для инвалидации кэша при мутациях.
- Используйте transformResponse, если нужно нормализовать или фильтровать ответ.
Пример улучшенного apiSlice с тегами:
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
export const productsApi = createApi({
reducerPath: "productsApi",
baseQuery: fetchBaseQuery({ baseUrl: "https://dummyjson.com/" }),
tagTypes: ["Products"],
endpoints: (builder) => ({
getAllProducts: builder.query({
query: () => "products",
providesTags: (result) =>
result
? [
...result.products.map((p) => ({ type: "Products", id: p.id })),
{ type: "Products", id: "LIST" },
]
: [{ type: "Products", id: "LIST" }],
}),
getProduct: builder.query({
query: (id) => `products/${id}`,
providesTags: (result, error, id) => [{ type: "Products", id }],
}),
addProduct: builder.mutation({
query: (newProduct) => ({
url: "products/add",
method: "POST",
body: newProduct,
}),
invalidatesTags: [{ type: "Products", id: "LIST" }],
}),
}),
});
export const { useGetAllProductsQuery, useGetProductQuery, useAddProductMutation } = productsApi;Кратко о тегах: providesTags помечает, какие части кэша покрывает query; invalidatesTags говорит RTK Query, что нужно обновить при мутации.
Настройка Redux store
RTK Query добавляет reducer и middleware, которые необходимо подключить в store.
Пример store.js:
import { configureStore } from "@reduxjs/toolkit";
import { productsApi } from "./features/apiSlice";
export const store = configureStore({
reducer: {
[productsApi.reducerPath]: productsApi.reducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(productsApi.middleware),
});Пояснение: middleware обеспечивает кеширование, подписки и инвалидацию. Без подключения middleware часть возможностей RTK Query работать не будет.
Компонент, использующий RTK Query
Пример компонента Data.js (перевод и объяснение):
import { useGetAllProductsQuery } from "../features/apiSlice";
import React, { useState } from "react";
import "./product.component.css";
export const Data = () => {
const { data, error, isLoading, refetch } = useGetAllProductsQuery();
const [productsData, setProductsData] = useState([]);
const handleDisplayData = () => {
refetch();
setProductsData(data?.products);
};
return (
{isLoading && Loading...}
{error && Error: {error.message}}
{productsData && productsData.length > 0 && (
{productsData.slice(0, 4).map((product) => (
-
Name: {product.title}
Description: {product.description}
Price: {product.price}
))}
)}
);
};Пояснения:
- RTK Query возвращает объект с состояниями: data, error, isLoading, isFetching и утилитами, как refetch.
- Частая ошибка — попытка читать data сразу после вызова refetch. refetch запускает новый запрос асинхронно; используйте isFetching/isLoading для UI.
Обновление App.js
Пример обёртки приложения (Provider + ApiProvider):
import "./App.css";
import { Data } from "./components/Data";
import { store } from "./store";
import { Provider } from "react-redux";
import { ApiProvider } from "@reduxjs/toolkit/query/react";
import { productsApi } from "./features/apiSlice";
function App() {
return (
);
}
export default App;Примечание: ApiProvider позволяет использовать RTK Query без прямого доступа к store, если вы предпочитаете более изолированную конфигурацию. В реализации выше Provider и ApiProvider используются вместе для совместимости и примера.
Когда RTK Query не лучший выбор
- Если у вас простые одноразовые запросы без кэширования — overhead может быть избыточным.
- Для очень специфичных потоков данных в реальном времени (WebSocket) RTK Query можно использовать как часть решения, но нативная поддержка потоков требует дополнительной логики.
- Если вы уже используете полный GraphQL-стек (Apollo Client) — RTK Query даёт преимущества для REST, но не заменит специфичных GraphQL-фич.
Альтернативы
- React Query (TanStack Query) — сильная библиотека для клиентского кэширования, не привязана к Redux.
- SWR — минималистичный подход от Vercel.
- Apollo Client — если вы используете GraphQL.
Короткая рекомендация выбора: выберите RTK Query, если вы уже используете Redux или хотите единое хранилище; выберите React Query, если не используете Redux и хотите независимую библиотеку.
Лучшие практики и эвристики
- Используйте providesTags/invalidatesTags для гарантированной инвалидации.
- Нормализуйте данные или используйте id-теги для минимальных повторных загрузок.
- Обрабатывайте ошибки глобально через слушатели middleware или через существующие UI-компоненты.
- Переход на TypeScript: опишите типы ответов и используйте generics для createApi и endpoints.
- Тестирование: мокаем HTTP с msw (Mock Service Worker) и используем mock store.
Ментальная модель: думайте о кэше как о локальном публичном репозитории данных — компоненты читают из него, мутации обновляют и инвалидаируют его.
Чек-лист по ролям
Разработчик:
- Добавил reducer и middleware в store
- Определил tagTypes и применил provides/invalidates
- Обработал состояния isLoading/isFetching/isError
- Написал unit/e2e тесты для запросов
Code reviewer:
- Проверил правильность reducerPath
- Проверил управление тегами и инвалидацию
- Убедился, что побочные эффекты не происходят в рендерах
QA:
- Провёл тесты отказоустойчивости (симуляция 500/timeout)
- Проверил кеширование (повторный запрос не должен обращаться к сети)
PM/PO:
- Подтвердил требования по времени обновления данных (SLA)
- Утвердил сценарии оптимистичных обновлений
Тесты и критерии приёмки
Критерии приёмки:
- Компонент корректно рендерит состояние загрузки, ошибки и данные
- Повторные вызовы для уже закэшированных данных не вызывают лишних сетевых запросов
- После мутации соответствующие query инвалидаются и данные обновляются
Минимальные тест-кейсы:
- На первой загрузке отображается Loading и затем список продуктов
- При симуляции 500 ошибки показывается сообщение об ошибке
- После addProduct обновляется список продуктов (инвалидация)
Snippets — быстрые подсказки (cheat sheet)
- Получить данные и подписаться:
const { data, error, isLoading } = useGetAllProductsQuery();- Запустить мутацию:
const [addProduct, { isLoading, error }] = useAddProductMutation();
await addProduct({ title: 'New', price: 100 });- Принудительный refetch:
const { refetch } = useGetAllProductsQuery();
refetch();Производительность и безопасность
- Кэширование снижает нагрузку на сеть, но следите за объёмом хранимых данных в store.
- Не храните в редьюсере чувствительные данные в открытом виде. Если передаёте токены, используйте защищённые механизмы и короткий срок жизни токенов.
- Ограничьте retention времени кэша при больших данных.
Миграция с React Query
Краткие шаги:
- Проанализировать используемые хуки и поведение кэша
- Перенести логику запросов в createApi endpoints
- Настроить теги для инвалидации
- Переписать usage на автогенерированные хуки
Сравнение (короткая матрица)
- RTK Query: отлично для проектов с Redux, встроенный кэш и интеграция
- React Query: гибкая, независимая, богатая экосистема
- SWR: простая и лёгкая
- Apollo: лучший для GraphQL
Частые ошибки и как их избегать
- Забыт добавить middleware в store — поведение кэша сломается.
- Использовать refetch и сразу читать data — результат ещё не придёт.
- Неправильно настроенные теги — инвалидация не сработает.
- Слишком долго хранить массивы в кэше — OOM/память.
Important: всегда используйте isFetching/isLoading для управления индикаторами загрузки, а данные берите из data только после успешного ответа.
Пример рабочего flowchart для выбора RTK Query
flowchart TD
A[Нужен ли централизованный state?] -->|Да| B{Используете Redux?}
A -->|Нет| C[Рассмотрите React Query или fetch]
B -->|Да| D[RTK Query — предпочтительно]
B -->|Нет| E[React Query или SWR]FAQ
Q: Можно ли использовать RTK Query без Redux Provider?
A: Да. ApiProvider позволяет использовать возможности RTK Query без прямого доступа к Redux store, но если вы хотите единое хранилище и использовать другие Redux-редьюсеры, подключайте Provider с store.
Q: Как тестировать компоненты с RTK Query?
A: Используйте msw для мокирования сетевых ответов и configureStore с подключённым productsApi.reducer и middleware для интеграционных тестов.
Заключение
RTK Query — мощный инструмент для работы с удалёнными данными в React-приложениях, особенно если вы уже используете Redux. Он снижает шаблонность кода, улучшает кэширование и делает управление состоянием API предсказуемым. Выберите RTK Query, если вам нужна тесная интеграция с Redux и централизованное управление данными.
Ключевые шаги, чтобы начать:
- Установить @reduxjs/toolkit и react-redux
- Создать apiSlice и добавить endpoints
- Подключить редьюсер и middleware к store
- Использовать сгенерированные хуки в компонентах
Если нужно, могу предоставить готовый репозиторий с примером, TypeScript-версией и тестами msw.
Похожие материалы
RDP: полный гид по настройке и безопасности
Android как клавиатура и трекпад для Windows
Советы и приёмы для работы с PDF
Calibration в Lightroom Classic: как и когда использовать
Отключить Siri Suggestions на iPhone