RTK Query: как вывести работу с данными в React на новый уровень

RTK Query: как вывести работу с данными в React на новый уровень

  • 19 сентября
  • читать 20 мин
Владимир Шайтан
Владимир Шайтан Technical Lead в Zoot, Преподаватель Компьютерной школы Hillel.
Юлия Яворская
Юлия Яворская Junior Front-End Developer в Ratifire

Запросы в веб-разработке — это как кровообращение в теле приложения. Без них ни один современный проект не сможет работать нормально. Вспомните все те моменты, когда вы пытались вывести данные на страницу, но сталкивались с кучей проблем: не те данные пришли, запрос завис, или вообще отправился не туда. Именно здесь на сцену выходят разные подходы к обработке запросов у React. И вот мы стоим перед выбором: использовать простой Fetch API, добавить немного магии с Axios или попробовать что-то мощнее, как RTK Query?

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

ЧТО ТЫ ЗНАЕШЬ О ЗАПРОСАХ? ОПРЕДЕЛЕНИЕ

Запрос в React — это процесс получения данных из внешнего источника (например, сервера) и их интеграции в пользовательский интерфейс. Это фундаментальная часть создания динамических веб-приложений, поскольку позволяет отображать актуальную информацию и реагировать на действия пользователя.

ТЕОРИЯ: ТИПЫ ЗАПРОСОВ В REACT

Запросы в мире React — это как выбор инструмента для ремонта. Можно взять самый обычный молоток, а можно вооружиться целым арсеналом особых инструментов.

Fetch API — это как стандартный набор инструментов, который есть у каждого разработчика. Это встроенный в браузер способ сделать запрос и получить данные. Прост, понятен, но иногда не очень удобен, когда начинаешь погружаться в более сложные сценарии.

Axios — это нечто вроде апгрейдированного молотка. Он может сделать всё то же, что и Fetch API, но с дополнительными «плюшками»: перехват ошибок, автоматическое преобразование данных и ещё куча полезных штук, значительно упрощающих жизнь разработчику.

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

Но RTK Query — не единственный «тяжеловес» в этом спорте. Вот несколько других аналогов, которые могут быть полезны:

  • SWR (стабильность и производительность): разработанный командой Vercel, SWR фокусируется на кэшировании и повторном использовании данных. Он прост в использовании, идеально подходит для приложений, где производительность и стабильность данных стоят на первом месте.
  • React Query (всё о хендлинге запросов): еще один мощный инструмент, специализирующийся на управлении состоянием серверных данных. React Query предлагает интуитивные интерфейсы для работы с кэшированием, синхронизацией данных и обновлением информации в реальном времени. Это отличный выбор для проектов, где серверные данные играют ключевую роль.
  • Apollo Client (для GraphQL): если ваше приложение работает с GraphQL, Apollo Client — ​​это то, что нужно. Он не только выполняет запросы, но позволяет полностью контролировать управление состоянием данных, обработку ошибок и оптимизацию запросов.

Выбор инструмента зависит от сложности проекта и потребностей вашей команды. Если требуется простой и эффективный способ делать запросы, Fetch API или Axios вполне могут справиться. А вот если перед вами стоит задача управления большим объёмом данных, следует обратить внимание на более специализированные решения, такие как RTK Query, SWR, React Query или Apollo Client.

RTK QUERY && RATIFIRE — ЧТО ЭТО?

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

Мой опыт с запросами, а именно через RTK Query, начался на проекте «Ratifire».

Ratifire — это платформа на базе современных технологий и имплементированных решений, разработанная группой энтузиастов. Для пользователя — это web application, где можно протестировать свои навыки среди IT community, получить официальное подтверждение в аккаунте и использовать при подаче на роль, что позволит потенциально уменьшить время на hiring. Сами плюсы как для компании, так и для кандидата.

Заинтересовала? Итак, вернёмся к теме запросов в React в разрезе этого проекта.

SETTING UP THE API SLICE

Основой RTK Query является API slice, определяющий базовую конфигурацию для API запросов. В файле 'apiSlice.js' мы начинаем с импорта необходимых модулей:

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import Cookies from 'js-cookie';
import { logOut } from '../../auth/authSlice';
  • createApi и fetchBaseQuery: это основные функции из RTK QuerycreateApi создаёт API slice, а fetchBaseQuery позволяет вам легко делать запросы к серверу с учётом базовых настроек, таких как URL базы или учётные данные (credentials);
  • Cookies: используется для управления сессионными cookie-файлами, помогающими, например, автоматически удалять сессию при неавторизованном доступе;
  • logOut: action, который вызывается, когда сессия пользователя заканчивается или возникает ошибка авторизации, и необходимо выйти из аккаунта.

Далее мы конфигурируем базовый query (запрос) с API URL и учётными данными (credentials):

const baseQuery = fetchBaseQuery({
 baseUrl: process.env.REACT_APP_API_URL,
 credentials: 'include',
});
  • baseUrl: устанавливает основную URL для всех запросов, передаваемых через это API. Используется переменная среды, чтобы сохранить конфигурацию гибкой и легко адаптированной под различные среды (например, разработка, тестирование, продакшн);
  • credentials: 'include': содержит учётные данные (например, cookie) во всех запросах, необходимых для аутентификации пользователей.

Далее мы добавляем логику для обработки неавторизованного доступа:

const baseQueryWithReauth = async (args, api, extraOptions) => {
 const result = await baseQuery(args, api, extraOptions);
 if (result.error && result.error.status === 401) {
   api.dispatch(logOut());
   Cookies.remove('JSESSIONID');
   return Promise.reject(result.error);
 }
 return result;
};
  • baseQueryWithReauth: это асинхронная функция, которая сначала выполняет запрос с помощью baseQuery, а затем проверяет, вернулась ли ошибка с кодом 401 (неавторизованный доступ). Если да, она вызывает logOut, чтобы выйти из аккаунта, и удаляет сессию с помощью Cookies.remove('JSESSIONID'). Если всё хорошо, возвращает результат запрос.

Наконец, создаём сам API slice:

export const apiSlice = createApi({
 baseQuery: baseQueryWithReauth,
 endpoints: (builder) => ({}),
});
  • createApi: создаёт API slice с использованием настроенного baseQueryWithReauth;
  • endpoints: функция, в которой определяются все конечные точки (endpoints) для этого API. Оставляем пустым, но позже сюда добавятся запросы, которые будут обрабатывать различные типы данных

CONFIGURING THE REDUX STORE

Теперь, когда мы настроили API slice, пора интегрировать его в наш Redux Store. Это позволяет использовать RTK Query непосредственно в компонентах React через Redux.

import { configureStore } from '@reduxjs/toolkit';
import logger from 'redux-logger';
import storage from 'redux-persist/lib/storage';
import { persistReducer, persistStore } from 'redux-persist';
import { FLUSH, PAUSE, PERSIST, PURGE, REGISTER, REHYDRATE } from 'redux-persist/es/constants';
import { apiSlice } from '../services/api/apiSlice';

// Other slice imports...
  • configureStore: это функция из Redux Toolkit, упрощающая создание Redux store с предварительно настроенными редюсерами и middleware;
  • logger: middleware, который логирует каждое действие в Redux, что полезно для дебага;
  • redux-persist: используется для сохранения состояния Redux в localStorage или другом хранилище для сохранения данных между перезагрузками страницы;
  • apiSlice: наш API slice, который мы только что создали. Он добавляется в корневой редюсер и middleware, чтобы интегрировать RTK Query в наше приложение.

Теперь мы определяем конфигурации для сохранения состояния и комбинируем их в rootReducer:

const authPersistConfig = {
 key: 'auth',
 storage,
 whitelist: ['user'],
};

// Other persist configs...

const rootReducer = {
 modal: modalSliceReducer,
 education: educationReducer,
 modalStep: modalStepReducer,
 [apiSlice.reducerPath]: apiSlice.reducer,
 auth: persistReducer(authPersistConfig, authReducer),
  // Other reducers...
};
  • authPersistConfig: это конфигурация для сохранения состояния авторизации пользователя. Сохраняется только user, обеспечивающий защиту чувствительной информации;
  • rootReducer: главный редюсер приложения, объединяющий все слайсы в один редюсер. Сюда также входит редюсер для apiSlice, чтобы RTK Query могла работать со своими данными.

И, наконец, настраиваем сам Store со всеми middleware:

const store = configureStore({
 reducer: rootReducer,
 middleware: (getDefaultMiddleware) =>
   getDefaultMiddleware({
     serializableCheck: {
       ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
     },
   })
     .concat(logger)
     .concat(apiSlice.middleware),
 devTools: true,
});

const persistor = persistStore(store);

export { store, persistor };
  • store: создаётся с помощью configureStore, с корневым редюсером и middleware. Включение apiSlice.middleware позволяет RTK Query обрабатывать запросы в приложении;
  • persistor: сохраняет состояние Redux, чтобы его можно было восстановить после перезагрузки страницы.

DEFINING API ENDPOINTS

После настройки API slice и Store можно определить конкретные endpoints для запросов.

import { apiSlice } from '../services/api/apiSlice';

export const workExperienceApiSlice = apiSlice.injectEndpoints({
   tagTypes: ['WorkExperience'],
   endpoints: (builder) => ({
       getWorkExperienceByUserId: builder.query({
           query: (userId) => `/users/${userId}/employment-records`,
           providesTags: (result) =>
               result
                   ? [...result.map(({ id }) => ({ type: 'WorkExperience', id })), 'WorkExperience']
                   : ['WorkExperience'],
       }),
       // Інші endpoints...
   }),
});

export const {
   useGetWorkExperienceByUserIdQuery,
   // Інші хуки...
} = workExperienceApiSlice;
  • injectEndpoints: позволяет добавить новые endpoints в существующий API slice. Здесь определены endpoints для получения опыта работы пользователя;
  • providesTags: используется для кэширования данных. Этот параметр помогает RTK Query определять, какие данные обновляются, когда они изменяются на сервере.

USAGE IN THE COMPONENT

Осталось только прописать использование нужного endpoint в нужном компоненте. В нашем проекте это блок, отвечающий за отрисовку всех записей об опыте работы, который мы получаем с backend:

import React from 'react';
import { Box } from '@mui/material';
import styles from './WorkExperience.styles';
import WorkExperienceItem from './WorkExperienceItem/WorkExperienceItem';
import { useGetWorkExperienceByUserIdQuery } from '../../../../redux/workExperience/workExperienceApiSlice';
import { useSelector } from 'react-redux';

const WorkExperience = () => {
 const { id } = useSelector((state) => state.auth.user.data);

 const { data: workExperiencesData } = useGetWorkExperienceByUserIdQuery(id);

 return (
   <Box sx={styles.container}>
     <Box>
       {workExperiencesData &&
         workExperiencesData.map(
           ({ id, startDate, endDate, position, companyName, description, responsibilities }) => {
             return (
               <WorkExperienceItem
                 key={id}
                 id={id}
                 startDate={startDate}
                 endDate={endDate}
                 position={position}
                 companyName={companyName}
                 description={description}
                 responsibilities={responsibilities}
               />
             );
           }
         )}
     </Box>
   </Box>
 );
};

export default WorkExperience;
  • useGetWorkExperienceByUserIdQuery: хук, сгенерированный RTK Query для получения данных об опыте работы пользователя. Он автоматически запрашивает сервер и возвращает ответ в виде data.
  • useSelector: используется для получения идентификатора пользователя из Redux store.

Благодаря этим шагам вы сможете легко интегрировать RTK Query в свой проект и удобно управлять запросами и состоянием данных.

ВЫВОДЫ

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

Используя RTK Query, вы получаете несколько ключевых преимуществ:

  1. Автоматизация и удобство: RTK Query берёт на себя большую часть работы с данными, что значительно упрощает управление состоянием и уменьшает количество ручного кода.
  2. Производительность: благодаря автоматическому кэшированию и оптимизированному обновлению данных, ваше приложение становится более быстрым и отзывчивым. Вы можете быть уверены, что данные всегда актуальны, а запросы не дублируются лишний раз.
  3. Масштабируемость: RTK Query хорошо работает как в маленьких проектах, так и в больших, где нужно управлять большим количеством запросов и данных. С таким инструментом ваш код остаётся чистым и поддерживаемым, даже когда проект растёт.
  4. Легкая интеграция: если вы используете Redux в своём проекте, интеграция RTK Query пройдёт максимально гладко. Он естественно вписывается в существующую архитектуру Redux, позволяющую быстро добавить новые возможности без значительных изменений в коде.
  5. Гибкость: при всей своей автоматизации, RTK Query остается гибким инструментом, позволяющим вам настроить каждый аспект обработки запросов в соответствии со своими потребностями. Вы можете легко создавать собственные запросы, настраивать обработку ошибок и работать с разными типами данных.

Однако важно помнить, что RTK Query не панацея, и его использование имеет смысл в тех проектах, где действительно нужна мощная работа с данными. Если у вас небольшой проект или запросы не являются критической частью бизнес-логики, возможно, стоит рассмотреть более лёгкие инструменты, такие как Fetch API или Axios.

С другой стороны, если ваше приложение требует сложной работы с данными, регулярного обновления информации или взаимодействия с большим количеством API, RTK Query станет вашим лучшим другом. С ним вы сможете забыть о многих рутинных задачах и сосредоточиться на создании крутых фич и улучшении опыта пользователей.

В заключение, если вы ещё не пробовали RTK Query, очень рекомендую выделить время, чтобы ознакомиться с этим инструментом и протестировать его на своём проекте. Возможно, он станет тем решением, которое вы давно искали, чтобы вывести свой код на новый уровень качества и эффективности.

СОВЕТЫ И ЛУЧШИЕ ПРАКТИКИ

Когда дело доходит до использования RTK Query, существует несколько моментов, которые следует иметь в виду, чтобы получить максимум от этого инструмента. Ниже приведены несколько советов и лучших практик, которые помогут вам эффективно использовать RTK Query в вашем проекте:

  1. Правильно настраивайте baseQuery:
    • Используйте fetchBaseQuery, чтобы быстро и легко настроить базовые запросы. Это значительно упростит ваш код и позволит сосредоточиться на самих запросах, а не на конфигурации.
    • Если ваше приложение требует обработки ошибок или повторной авторизации, создайте кастомизированный BaseQuery с дополнительными проверками, как это было показано в примере BaseQueryWithReauth.
  2. Кешируйте данные разумно:
    • Используйте providestags и invalidatestags, чтобы контролировать, какие данные должны кэшироваться и когда они должны обновляться. Это поможет избежать лишних запросов к серверу и обеспечит актуальность данных.
    • Определяйте теги для каждого типа данных, чтобы иметь полный контроль над их обновлением и очисткой кэша.
  3. Оптимизируйте работу с запросами:
    • Помните, что RTK Query автоматически оптимизирует повторные запросы к одним и тем же данным. Используйте это, чтобы уменьшить нагрузку на сервер и улучшить производительность вашего приложения.
    • Используйте функцию refetchOnMountOrArgChange, когда необходимо автоматически обновлять данные при определённых условиях, например, при изменении параметров или повторном посещении страницы.
  4. Анализируйте состояние запроса:
    • Всегда проверяйте состояние вашего запроса (isLoading, isError, isSuccess) перед рендерингом компонента. Это поможет избежать некорректного отображения данных и улучшит пользовательский опыт.
    • Используйте skipToken для условного пропуска запросов, когда данные пока не нужны или их нужно загрузить позже.
  5. Используйте RTK Query DevTools:
    • Если вы работаете в среде разработки, включите DevTools для RTK Query. Это поможет вам отслеживать все запросы, проверять кэш, видеть статусы запросов и значительно облегчит отладку приложения.
  6. Добавьте кастомную логику к middleware:
    • Если у вас есть специфические требования к обработке данных, рассмотрите возможность добавления кастомного middleware в Redux. Это позволит вам дополнительно обрабатывать данные после того, как RTK Query уже выполнило запрос.
  7. Документируйте свои API endpoints:
    • Не забывайте документировать ваши endpoints. Это поможет вам и вашей команде быстрее ориентироваться в коде, особенно когда проект растёт и становится сложнее.
  8. Используйте RTK Query для рефакторинга существующего кода:
    • Если у вас уже есть код с большим количеством запросов, рассмотрите возможность постепенного переноса этих запросов на RTK Query. Это позволит вам воспользоваться всеми преимуществами нового инструмента без необходимости переписывать весь проект с нуля.
  9. Не забывайте о тестировании:
    • Тестируйте ваши запросы и интеграции с RTK Query. Используйте моки и специальные библиотеки для тестирования запросов, чтобы убедиться, что ваш код работает стабильно во всех условиях.
  10. Постоянно обновляйтесь
    • RTK Query активно развивается, следите за обновлениями и новыми функциями. Это поможет вам быть в курсе лучших практик и использовать все возможности инструмента по полной.

Рекомендуем публикации по теме