Галоўная / Артыкулы / React Query і Redux: перацоўка над станам сервера ў большых прыкладах практычнага выкарыстоўвання

React Query і Redux: перацоўка над станам сервера ў большых прыкладах практычнага выкарыстоўвання

Дазвольце дазнаць, чаму аплікацыя для чатавання выкорыстоўвала TanStack Query заместа Redux для керавання дадзеннямі сервера, і якую ролю Redux все ўсё выпало граць у сучасной архітэктуре React.

2701 слоў

Уявіце разработчыка, які пераходзіць з меньшых проектаў у компанію, яка стварае продукты, і які не можа дачакацца, каб нарэшце пазнакоміцца з тым, як на самаўялікіях масштабах ствараецца програмнае забезпечэння высокай якосці. Пасля гадоў роботы над фронтэнд-застосункамі працаванне ў команде, якая адпаведальна за програма, якую викорыстоўваюць мільйоны людзей, зменяе спосаб, якім вы оцэнюеце код.

Вы перестаёте проста запытацца: "Чы гэтае функцыя працюе?"

У замен вы пачынаеце запытацца:

Як гэтая архітектура вытрымае навантажэння мільйоноў корыстувачаў? Як падтрымліваецца сінхроннасць стану ў всім застосунке? Што будзе, калі дзесяць разных компанентаў патрабуюць тых сабе данных? Як інжынеры падтрымліваюць такую вялікую базу коду, калі продукт продовжае растаць?

Тепер уявіце, што гэтаму разработчыку паставяюць задачу працаваць над адным з ключоўых модуляў продукту — чат-застосункам, схожым за принцыпам на Slack.

Это не якась незначная функцыя, якая знаходзіцца ў канцы аплікэшну.

Аператыў спілкування знаходзіцца ў цэнтры продукту, служычы мільйонам корыстувачаў і тычучыся безпосереднія да важлівых бізнес-процэсаў, фактараў, якія прыносяць дохаду, а таксама да найважлівейшых корпаратыўных кліянтаў компаніі.

Таму ўсё логічна, што калі гэты разработчык упершыню працаваў з базай коду, ён меў досыць чаканыя прагнуткі.

Аплікэшн для спілкування такога масштабу нехтае з вялікай колькасцю інфармацыі, яку павінны раздзеляць многія экраны: тэксты пераходаў і ўсіх паведамленняў, колькі паведамленняя застаўся непрачытаным, хто берае участь у размове, можлівасць праходжэння па історыі, змены і іншыя операцыі запісу, паказчыкі завантажэння і реальныя апдэйты, якія прыходзяць у рэальны час.

Усё гэта значыць, што ў вялікай базе коду React можна спадзявацца знайсці стандартныя элементы:

Redux, Zustand або прынеймна якась форма каральнага адміністрыравання стану.

Але пасля анаізу коду не было знайдзена жадной базы дадзеных.

Няма налаштавання Redux.

Няма Zustand.

Няма вялікаг об’екта Context, який бы зберагаў спільныя даныя прыкладнення.

На першы погляд можна было б прыпусціць, што проста чагосці не было знайдзена падчас аналізу репазітарыя.

Аднак глэбэйшы анаіз паказвае, што на самай працэ выканання спільнага слоя дадзеных вплывае іншы элемент.

TanStack Query.

Это сапраўды неспадзяваная нарадка, якщо ваша уява пра гэтую бібліятэку є обмежанай.

Багато разработчыкаў спачатку вважаюць React Query прасадою для запрашання дадзеных з API, кэшавання адпаведзей, стежэння за статусам загрузкі і павторнага запрашання, калі ў чымсці з’яўляюцца змены.

Адзначыце, у гэтым прыладзе высокага рангу, які служыць мільйонам корыстнікаў, кэш запытанняяваліся набагато больш, чым тое. Ён выступаў як спяльны база для всіх дадзеных, якія належаць серверу, у всім прыладзе.

Гэтыя спазыраванні ставяць пад сумнев давно існуючую прыпуск пра архітектуру фронтэнду.

Можа, правыя запытанні не ў таму:

"Чаму тут няма магазіна Redux?"

Можа, іх следуець задаваць так:

"Чаму дадзеныя, якія належаць серверу, ўжо з самага пачатку трэба хаваць у Redux?"

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

З Redux да React 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 выступае як шар між вашым UI і гэтым бэкэндам, беручы на сябе адпаведальнасць за жыцёвы цикл гэтых даных.

У адной з основ гэтага дзяйства стоіць 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
})

Это не проста зменшэння колькасці рядкоў коду.

Сама запитання стае абстракцыяй, якая абгортае ресурс сервера.

Імены ключоў запитання называюць ресурс, які стоўіцца пад аблікаванне.

Кэш зберагае рынак.

Запитання стежыць за своім статусам.

І будолькі компонентаў можуць выкарыстоваць той самы кэшаваны значэнне.

У TanStack Query элемент QueryCache створаны спецыяльна для зберагчэння рэзультатаў запытаў разам з іх асоціяваным станам, а 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, як яны выделяюць та дзеляць логіку з статаусам между компонентамі, а таксама якія распашчытныя памылкі трэба ухіляцца пад час ўтварэння такіх хуків.
  • Power Apps проты React: парабяранне дугагалоўскіх костаў та архітэктуры — У этай статыце аналізуюцца схованыя косты ліцэнзавання, архітэктурныя компромісы та практычныя аспекты керавання, якіе важнаюць для выявлення таго, чы рэальна Power Apps ці React є дешавейшымі при масовым выкарыстоўванні.
  • Перазьявленне падходу да React State: Дзе насправды павінны знаходзіцца вашы даны — У гэтым артыкуле пояснюецца, як зменшыць калекі ў React, пераносячы стацус у URL, DOM чыю выведзеныя значэння замест таго, каб часта викорыстоўваць useState.