Как настроить RTK Query
19 июня 2023 г.
RTK Query - это инструмент Redux Toolkit. При его использовании нет необходимости создавать отдельные state, action, dispatch для получения данных с сервера. Он все сделает сам. Он кеширует и обращается к серверу лишь в том случае, если данные изменились. Все примеры в заметке относятся к приложению на React. Я описываю далеко не все возможности инструмента, поэтому для более детального погружения заглядывайте в документацию.
(Приведенные названия методов могут меняться при обновлении Redux Toolkit)
Документация: https://redux-toolkit.js.org/rtk-query/overview
Настройка
Устанавливаем в проект Redux Toolkit. командой npm install @reduxjs/toolkit
Для создания провайдера и передачи store в index.js, я использовать react-redux
npm install react-redux
Как обычно делают в проектах при использовании redux.
1import React from 'react';2import * as ReactDOMClient from "react-dom/client";3import { Provider } from "react-redux";4import './index.scss';5import ErrorBoundry from './components/error-boundry';6import App from './App';78const store = setupStore();910const container = document.getElementById("root");11const root = ReactDOMClient.createRoot(container);1213root.render(14 <Provider store={store}>15 <ErrorBoundry>16 <App />17 </ErrorBoundry>18 </Provider>19);20
Создание api
Создаем файл, в котором будем писать функции RTK Query. Например, cards.js. В него импортируем следующее:
1import {createApi, fetchBaseQuery} from "@reduxjs/toolkit/dist/query/react";
И создаем API следующим образом:
1const api_uri = process.env.REACT_APP_API;23export const cardsAPI = createApi({4 reducerPath: 'cardsAPI',5 baseQuery: fetchBaseQuery({baseUrl: api_uri}),6 tagTypes: ['Cards'],7 endpoints: (build) => ({8910 })11})12
Для того, чтобы создать GET запрос, используем build.query.
Документация: https://redux-toolkit.js.org/rtk-query/usage/queries
1export const cardsAPI = createApi({2 reducerPath: 'cardsAPI',3 baseQuery: fetchBaseQuery({baseUrl: api_uri}),4 tagTypes: ['Cards'],5 endpoints: (build) => ({6 fetchAllCards: build.query({7 query: ({limit = 10, orderBy = '0', skip = 0} ) => ({8 url: `/cards`,9 params: {10 skip: skip,11 limit: limit,12 order_by: orderBy13 }14 }),15 providesTags: result => ['Cards']16 }),17 fetchCard: build.query({18 query: ({id = 0} ) => ({19 url: `/cards/${id}`,20 }),21 providesTags: result => ['Cards']22 }),23 })24})
Для того, чтобы отправить POST, PUT запрос (и т.д.), или если надо использовать запрос не при загрузке страницы, используем build.mutation.
Документация: https://redux-toolkit.js.org/rtk-query/usage/mutations
1 editCard: build.mutation({2 query: ({body, id}) => ({3 url: `/cards/${id}`,4 method: 'PATCH',5 body6 }),7 providesTags: result => ['Cards']8 }),
Получаемый результат можно обрабатывать. Для этого существуют специальные функции - transformResponse, transformErrorResponse.
1 fetchPosts: build.query({2 query: ({limit = 10, skip = 0}) => ({3 url: `/posts`,4 params: {5 skip: skip,6 limit: limit7 }8 }),9 providesTags: result => ['Post'],10 transformResponse: (response, meta, arg) => response.posts,11 }),12
Теги
providesTags
Указываем в query запросах. Этот тег закрепляется за кэшированными данными, которые возвращаются в запросе. Можно по различному манипулировать записью данными в кэш. Доступные форматы можно посмотреть в документации.
Самый простой пример: providesTags: [`Posts`]
Через этот тег в mutation запросе обновляются данные в кэше.
invalidatesTags
Используем только в mutation запросах. В нем указываем тот тег/теги, который указали в providesTags, данных, которые необходимо обновить.
Самый простой пример: invalidatesTags: [`Posts`]
Настройка store
После создания api, настраиваем store.
В новый файл импортируем combineReducers, configureStore и созданное api.
1import {combineReducers, configureStore} from "@reduxjs/toolkit";2import {cardsAPI} from "../services/cardsAPI.js";
Создаем rootReducer и setupStore.
1const rootReducer = combineReducers({2 // можно добавить и обычные редьюсеры redux3 [cardsAPI.reducerPath]: cardsAPI.reducer,4})56export const setupStore = () => {7 return configureStore({8 reducer: rootReducer,9 middleware: (getDefaultMiddleware) =>10 getDefaultMiddleware()11 .concat(cardsAPI.middleware)12 })13}
ИспользованиеRTK Query в компонентах
Получение данных
В компоненте можем получить:
- данные,
- события ошибки - булиновое значение и текст,
- значение загружаются ли данные или нет,
- значение успешной загрузки данных.
- функцию сброса булиновых значений - reset (в мутации).
Query
Если мы использовали метод build.query() при создании запроса, то метод запроса будет называться по следующей схеме : use + наше название запроса с большой буквы + Query;
В этом случаем мы деструктурируем объект и достаем из него нужные данные.
Запрос срабатывает при создании компонента. В него можно передать аргументы и опции. В том случае, в котором написан наш api, аргументы передаются как:
1const {data: cards,2 error: errorCards,3 isLoading: isCardsLoading,4 isSuccess: isCardsSuccess,5 } = CardsAPI.useFetchAllCardsQuery();67 const {data,8 error,9 isLoading,10 isSuccess,11 } = CardsAPI.useFetchAllOptionsQuery({limit: OptionsLimit}, {skip: OptionsSkip});
В опциях можем преобразовать результат запроса:
1const {card,2 error,3 isLoading,4 } = projectAPI.useFetchCardQuery({id: cardId}, {5 selectFromResult: ({ data, error, isLoading}) => {6 // Здесь могут быть различные преобразования7 return ({8 card: data?.card[0],9 error: error,10 isLoading: isLoading11 })12 },13 skip14 });
Mutation
Если использовали метод build.mutation() при создании запроса, то метод запроса будет называться по следующей схеме : use + наше название запроса с большой буквы + Mutation;
В случае с мутацией, деструктурируем массив, который включает функцию мутации (называется как запрос) , и объект с данными запроса.
1const [editCard,2 {error,3 isError,4 isLoading,5 isSuccess,6 reset}] = CardsAPI.useEditCardMutation();
Функцию reset я, например, использовала для того, чтобы сбросить события отправки после закрытия модального окна и убрать все сообщения. Так как например, статус успешной отправки сохраняется true, если его не сбрасывать.
Функцию мутации можем использовать как обычную функцию, передавая в нее необходимые данные для запроса.
Например, поменяли название карточки и отправляем запрос при отправке формы:
1const onEditCard = (e) => {2 e.preventDefault();3 if(name) {4 const body = {5 "name": name6 }78 editCard({body});9 }10 }
Для того, чтобы не заключать объект в новый объект, можно использовать метод .unwrap().
1 editCard(body).unwrap();
Также .unwrap() используем, когда необходимо указать .then()
1confirm(data)2.unwrap()3.then(() => {4 setShow(false);5});
Скипнуть запрос, если данные, необходимые для запроса, еще не подгрузились
Я создала отдельную переменную для опции skip, которую мы передаем в запрос. И по умолчанию задала ей значение true.
1const [skip, setSkip] = useState(true);2const {data} = PosttAPI.useFetchPostQuery({postId}, {skip});
В useEffect слежу за id, необходимым для получения данных по посту и если id есть, переключаю skip в значение false и запрос вызывается.
1useEffect(() => {2 if(postId) {3 setSkip(false)4 }5}, [postId])
Получить файл с сервера и скачать его
В моем случае, я преобразовала файл в object url в функции запроса.
1getFile: build.mutation({2 query: ({id}) => ({3 url: `/file`,4 method: 'GET',5 params: {67 id: id,8 },9 responseHandler: async (response) => window.URL.createObjectURL(await response.blob()),10 cache: "no-cache",11 }),12 providesTags: result => ['File'],13 }),14
На странице компонента, создаем ссылку, в href задаем значение той ссылки, которую получили выше. В атрибуте download указываем, как должен называться скачиваемый файл. Добавляем ссылку в body и создаем событие клика на ссылку.
1const [getFile, {}] = siteAPI.useGetFileMutation();23 const getFile = async () => {4 if(id) {5 const url = await getFile({id});6 const link = document.createElement('a');7 link.setAttribute('href', url?.data);8 link.setAttribute(9 'download',10 `fileName.html`,11 );1213 // Append to html link element page14 document.body.appendChild(link);1516 // Start download17 link.click();1819 // Clean up and remove the link20 link.parentNode.removeChild(link);21 }22 }
Я описала далеко не все возможности RTK Query, поэтому изучайте документацию и используйте! :)