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 активно розвивається, тож стежте за оновленнями та новими функціями. Це допоможе вам бути в курсі найкращих практик і використовувати всі можливості інструменту на повну.

Рекомендуємо публікації по темі