React Query та Redux: переосмислення стану сервера у великих додатках
Дізнайтеся, чому у додатку для чату використовується TanStack Query замість Redux для керування даними сервера, та де все ще знаходить своє місце Redux у сучасній архітектурі React.
Уявіть розробника, який переходить від менших проєктів до компанії, що працює над продуктами, і який дуже хоче побачити, як насправді створюється програмне забезпечення великого масштабу та промислового рівня. Після багатьох років роботи над фронтенд-застосунками приєднання до команди, відповідальної за програмне забезпечення, яким користуються мільйони людей, змінює спосіб оцінки коду.
Ви перестаєте просто запитувати: «Чи функціонує ця функція?»
Натомість ви починаєте запитувати:
Як ця архітектура витримує навантаження від мільйонів користувачів? Як підтримується синхронність стану в усьому застосунку? Що відбувається, коли десять різних компонентів потребують одних і тих самих даних? Як інженери підтримують таку велику базу коду у придатному стані, коли продукт продовжує розвиватися?
Тепер уявіть, що цьому розробнику доручили один із ключових модулів продукту — чат-застосунок, схожий за принципом на Slack.
Це не якась дрібна функція, прихована в кутку додатка.
Досвід чату є серцем продукту, який обслуговує мільйони користувачів та тісно пов’язаний із критичними бізнес-процесами, механізмами збільшення доходу та найважливішими корпоративними клієнтами компанії.
Тож цілком логічно, що під час першого ознайомлення з кодовою базою цей розробник мав досить очікувані уявлення про те, що його чекає.
Додаток для чату такого масштабу неминуче має справу з величезною кількістю інформації, яку потрібно поширювати між багатьма екранами: теми чатів та їхні повідомлення, кількість непрочитаних повідомлень, учасники розмови, перегляд історії чату, зміни та інші операції з записом даних, індикатори завантаження та актуальні оновлення, які надходять у реальному часі.
З огляду на все це, можна очікувати знайти у великій кодовій базі React звичайні елементи:
Redux, Zustand або принаймні якась форма управління глобальним станом.
Але після перегляду коду не знайшлося жодного магазину даних.
Жодної налаштування Redux.
Жодного Zustand.
Жодного об’єкта Context, який би зберігав спільні дані додатку.
На перший погляд може здатися, що під час огляду репозиторію просто щось пропустили.
Однак більш детальний аналіз розкриває те, що насправді керує шаром спільних даних.
TanStack Query.
Це справді несподіване відкриття, якщо ваше уявлення про цю бібліотеку є обмеженим.
Багато розробників вважають React Query переважно інструментом для отримання даних з API, кешування відповідей, відстеження статусу завантаження та повторного отримання даних при змінах.
Проте в цьому продакшн-готовому додатку, який обслуговує мільйони користувачів, кеш запитів виконував набагато більше функцій. Він слугував спільним сховищем для всіх даних, що належать серверу, у всьому додатку.
Це спостереження ставить під сумнів давно існуючу припущення щодо архітектури фронтенду.
Можливо, правильне запитання не є таким:
"Чому тут немає сховища Redux?"
Можливо, воно має звучати так:
"Чому взагалі дані, що належать серверу, мають знаходитися в Redux?"
Це запитання відкриває можливість для набагато глибшого обговорення того, як ми моделюємо стан у додатках на React, та чому в багатьох реальних системах TanStack Query може тихо усунути значну частину механізмів глобального стану, які команди історично будували вручну.
Був період, коли підключення API-запиту до React-додатку здавалося простим завданням.
Потім з’явився Redux.
Сам API-запит залишався простим. Усе, що навколо нього було створено, ускладнювалося.
Спочатку потрібно було визначити дію.
Написати редьюсер.
Додати позначки завантаження та помилок.
Відправити цю дію.
Зберегти відповідь у магазині даних.
Написати селектор для її отримання.
Під’єднати компонент до всього цього.
А через кілька тижнів чи місяців хтось неминуче запитує:
„Чому ці дані виглядають застарілими?“
І зазвичай виправленням є ще одна дія, яка змушує перезавантажити дані.
Пройшовши через цей цикл багато разів, стає очевидним некомфортний патерн:
Інструменти глобального стану часто використовувалися для керування тим, що спочатку взагалі не належало до компетенції клієнтського стану.
Бекенд був справжнім власником даних.
Фронтенд лише їх використовував.
Саме ця різниця є важливою причиною того, чому TanStack Query став настільки цікавою альтернативою у сучасних React-додатках.
Спочатку: що саме таке React Query?
Перш ніж розглянути, як він зменшує потребу у Redux, варто прояснити поширене непорозуміння.
TanStack Query, раніше відомий як React Query, не є заміною useState, Redux чи Zustand.
Його основною функцією є керування серверним станом — даними, які походять ззовні вашого React-додатку та потребують завантаження, кешування, синхронізації, оновлення та зрештою вважаються застарілими.
React Query — це не просто відправка запиту та зберігання відповіді у локальному стані компонента.
Власна документація TanStack описує бібліотеку саме через завантаження, кешування, синхронізацію та оновлення стану сервера.
Уявіть собі дані такими:
Users
Projects
Messages
Notifications
Orders
Analytics
Ваш фронтенд насправді не володіє жодною з цих інформаційних одиниць.
Це робить бекенд.
React Query виступає як шар між вашим інтерфейсом та цим бекендом, беручи на себе відповідальність за життєвий цикл цих даних.
У основі цього дизайну лежить Query Cache.
Згідно з поточною документацією TanStack, QueryCache є шаром зберігання запитів — він зберігає їхні дані, метадані та статус. QueryClient керує цим кешем та надає API, які застосунок використовує для читання з нього, оновлення, скасування дійсності записів та іншої взаємодії з ним.
Саме тому кілька незалежних частин додатку можуть звертатися з однаковим запитом та отримувати послідовні результати:
const { data } = useQuery({
queryKey: ['projects'],
queryFn: fetchProjects
})
Проте React Query робить набагато більше, ніж просто кешує одну відповідь.
Він керує кешуванням, усуненням дублікатів запитів, відстеженням актуальності, фоновим оновленням даних, логікою повторних спроб, збиранням сміття, мутаціями та анулюванням даних, усім цим як частиною однієї системи.
Наприклад, після оновлення проекту:
const queryClient = useQueryClient()
await updateProject(project)
queryClient.invalidateQueries({
queryKey: ['projects']
})
Замість того, щоб вручну пояснювати десяти окремим компонентам, як оновлювати їхню локальну копію проекту, ви просто повідомляєте систему запитів:
„Дані сервера, які лежать в основі цього запиту, можуть більше не бути точними.“
Потім кеш самостійно займається їх перевіркою та оновленням.
Це основна ідея, яка лежить в основі TanStack Query.
Він не намагається стати другим Redux.
Це надає стану сервера власного життєвого циклу.
Як тільки ви починаєте розглядати стан сервера як щось принципово відмінне від стану клієнта, стає набагато простіше зрозуміти, чому додаток, який на перший погляд здається потребуючим величезного сховища даних Redux, насправді може взагалі не потребувати його.
Redux ніколи не був проблемою
Чесно кажучи, сам Redux тут не винен.
Redux Toolkit залишається офіційно рекомендованим підходом для створення додатків на Redux, і Redux все ще є корисним тоді, коли додатку справді потрібен складний стан з боку клієнта, передбачувані переходи між станами, канали проміжного програмування чи єдина модель стану.
Проблеми починаються тоді, коли розробники поміщають абсолютно все у єдине глобальне сховище даних.
{
user: {},
projects: [],
teams: [],
notifications: [],
orders: [],
products: [],
analytics: {},
theme: "dark",
sidebarOpen: true
}
На перший погляд здається, що все це належить додатку.
Але це не так.
Спробуйте поставити одне просте запитання:
Хто насправді є власником цих даних?
Чи контролює додаток React список проектів?
Не зовсім.
Справжнім власником є бекенд.
Чи може інший користувач змінити замовлення, поки ваша вкладка браузера залишається відкритою?
Звісно, може.
Чи може з’явитися сповіщення без жодних дій з боку вашого коду React?
Так, абсолютно.
Чи може сервер односторонньо скасувати або змінити права користувача?
Без сумніву.
Отже, значна частина цього „стану додатку“ зовсім не належить фронтенду.
Це стан сервера.
Стан сервера створює зовсім іншу категорію викликів.
Вам потрібно його отримати.
Вам потрібно його зберегти у кеші.
Вам потрібно з’ясувати, коли він стає застарілим.
Вам потрібно отримати його знову.
Після внесення змін вам потрібно підтримувати синхронізацію.
Вам потрібно керувати індикаторами завантаження та станами помилок.
Вам потрібно враховувати повторні спроби та перервані мережеві з’єднання.
Саме цю низку проблем було створено для вирішення за допомогою TanStack Query.
Архітектурні зміни
Традиційна конфігурація, орієнтована на Redux, зазвичай має такий алгоритм роботи:
API
↓
Async action / thunk
↓
Reducer
↓
Redux Store
↓
Selector
↓
React Component
Для багатьох додатків, що базуються на API, цей алгоритм може бути перетворений на щось на кшталт:
API
↓
TanStack Query
↓
Query Cache
↓
React Component
На папері різниця може здаватися незначною.
Це не так.
Справжня зміна полягає у тому, що ви більше не створюєте вручну всю необхідну інфраструктуру для керування станом сервера.
Візьмемо щось настільки звичайне, як список проєктів.
У Redux зазвичай починають так:
const initialState = {
data: [],
loading: false,
error: null
}
Потім додається асинхронна дія:
dispatch(fetchProjects())
Далі йде логіка редюсера, яка охоплює:
pending
fulfilled
rejected
І нарешті — селектор:
const projects = useSelector(
state => state.projects.data
)
Тепер порівняйте це з версією TanStack Query:
const { data, isPending, error } = useQuery({
queryKey: ['projects'],
queryFn: fetchProjects
})
Це не просто скорочення кількості рядків коду.
Сам запит стає абстракцією, яка обгортає ресурс сервера.
Ім’я ключа запиту позначає ресурс, який відстежується.
Кеш зберігає результат.
Запит стежить за власним статусом.
І будь-кілька компонентів можуть отримувати дані з цього ж кешованого значення.
QueryCache у TanStack Query існує саме для зберігання результатів запитів разом із їхнім пов’язаним станом, а QueryClient надає інтерфейс для роботи з цим кешем.
Кеш запитів — це по суті глобальний сховище даних сервера
Це, мабуть, найважливіша концепція тут.
Багато розробників чують таку фразу:
"У React Query є кеш."
І вважають, що це означає:
"Це просто кешування відповідей API."
Але насправді це значно більше.
Кеш запитів фактично стає спільним, єдиним джерелом істини для стану сервера вашого додатку.
Dashboard
|
+── ProjectList
|
+── ProjectSidebar
|
+── RecentProjects
Кожен з них потребує одних і тих самих даних, ключованих за:
['projects']
Немає потреби вручну передавати ці дані з сервера до Redux, а потім змушувати всі три компоненти читати їх з магазину даних.
Достатньо лише запитувати однаковий запит там, де це необхідно:
useQuery({
queryKey: ['projects'],
queryFn: fetchProjects
})
TanStack Query самостійно займається обміном та кешуванням цих даних у фоновому режимі.
Результатуюча архітектура може виглядати так:
React App
|
┌─────────┴─────────┐
| |
Client State Server State
| |
Redux / Zustand TanStack Query
| |
UI state Query Cache
Раптово весь системний підхід стає набагато простішим для розуміння.
Чи може React Query замінити Redux?
У деяких додатках — так, це справді можливо.
Але ось ключова нюанса:
Він не замінює Redux, будучи його вдосконаленою версією.
Натомість він усуває потребу використовувати Redux для керування станом сервера з самого початку.
Це значно інша твердження.
У власній документації TanStack Query прямо описує управління станом сервера як окрему проблему від управління станом клієнта, зазначаючи, що як тільки стан сервера переходить під керування React Query, обсяг стану клієнта, який потрібно керувати глобально, може значно зменшитися.
Саме тут справи починають ставати справді цікавими з точки зору архітектури.
Ви можете дійти до такого розподілу:
Client State
theme
sidebar
selectedTab
modal
editor
filters
Окрім цього:
Server State
users
projects
orders
notifications
products
analytics
На цьому етапі вибір інструментів стає набагато більш цілеспрямованим:
Client State → Redux / Zustand / Context / React
Server State → TanStack Query
Замість того, щоб очікувати, що один магазин буде виконувати обидві функції одночасно.
Але у Redux все ще є завдання
Розгляньмо інший тип додатку, щось більш схоже на інструмент для дизайну, як-от Figma.
Ви можете опинитися зі станом, який зберігається таким чином:
{
selectedLayer,
activeTool,
zoom,
canvasMode,
dragState,
undoStack,
redoStack
}
Жоден з цих елементів не є станом сервера.
Фронтенд повністю контролює його.
Він змінюється синхронно, безпосередньо у відповідь на дії користувача.
Кілька частин інтерфейсу одночасно залежать від нього.
Іноді може знадобитися координувати складні переходи, коли один елемент стану впливає на інший.
Саме у таких ситуаціях корисним є спеціалізований менеджер стану клієнта.
У документації TanStack Query також зазначається, що складний стан інтерфейсу, який не пов’язаний із сервером, все одно може стати підставою для використання спеціального інструменту для керування станом клієнта.
Отже, висновок не полягає у тому, щоб «видалити Redux скрізь та замінити його на React Query». Це було б надмірною корекцією.
І ще один важливий учасник: RTK Query
Є ще одна важлива деталь, яку варто згадати.
Redux Toolkit вже постачається з RTK Query — шаром для отримання даних та кешування, розробленим спеціально для роботи всередині додатків на Redux.
Він може автоматично генерувати хуків та керувати отриманням даних з кінцевих точок, статусами завантаження та кешуванням, подібно до TanStack Query.
Отже, справжня зміна в екосистемі полягає не просто у:
Redux → React Query
Це більше схоже на таку еволюцію:
Manual API state in Redux
↓
Dedicated server-state solutions
↓
TanStack Query / RTK Query / Apollo / SWR
Вся галузь поступово усвідомлює, що стан сервера та стан клієнта є фундаментально різними обов’язками, які вимагають різних інструментів.
Як тільки ви усвідомите цю різницю, ваша загальна архітектура стану стане значно простішою для розуміння.
Правило, яке я зараз використовую
При вирішенні, чи дані належать до глобального стану, одне запитання вирішує більшість проблем:
Хто насправді володіє цими даними?
Якщо відповідь така:
Бекенд
ви майже напевно працюєте зі станом сервера.
Якщо відповідь така:
Фронтенд
ви майже напевно працюєте зі станом клієнта.
Це просте запитання зазвичай спрямовує вас на зовсім іншу архітектуру залежно від ситуації.
Наприклад:
Current user ────────── Server
Projects ────────────── Server
Orders ──────────────── Server
Notifications ───────── Server
Theme ───────────────── Client
Modal ───────────────── Client
Selected tab ────────── Client
Editor state ────────── Client
Як тільки ви це так сформулюєте, правильна структура стає очевидною.
React Query не замінює Redux
Твердження про те, що «React Query замінює Redux», є дещо оманливим у своїй формулюванні.
Насправді відбувається щось більш тонке:
Розробники краще розуміють, з якою категорією стану вони насправді працюють.
Раніше Redux був стандартним місцем зберігання всього, включаючи відповіді сервера.
Все частіше команди розділяють:
Server state
↓
TanStack Query
з:
Client state
↓
Redux / Zustand / Context / React
Для багатьох сучасних проектів на React саме це розділення усуває значну частину складності, яку раніше приписували самому Redux.
Мета не в тому, щоб зменшити кількість бібліотек, які ви використовуєте.
Мета — припинити вручну створювати інфраструктуру для проблем, які вже мають готові, спеціально створені абстракції.
Тож наступного разу, коли ви натрапите на перевантажений фрагмент Redux, наповнений відповідями API, прапорцями завантаження, логікою скасування кешу та діями перезавантаження, запитайте себе:
Чи справді вам потрібен глобальний менеджер стану для цього, чи ви просто перереалізували React Query вручну всередині Redux?
Як ви вирішуєте цю проблему у своїх додатках?
Чи зберігаєте ви наразі дані сервера у Redux чи Zustand, користуєтеся TanStack Query, чи обираєте зовсім інший підхід?
Пов’язана література
- Десять прихованих проблем компонентів React, які уповільнюють сучасні додатки — Дізнайтеся про десять поширених помилок у компонентах React — від проблем із семантичним HTML до відсутності мемоїзації — та про способи їх виправлення для збереження швидкості, доступності та відсутності помилок у додатках у 2026 році.
- Виправлення проблеми перевантаження параметрами у React за допомогою композиції та слотів — Дізнайтеся, чому параметри React із великою кількістю налаштувань створюють проблеми з технічним обслуговуванням, та як інверсія керування, композиція та слоти дозволяють створювати справді повторно використовувані компоненти.