Структураванне дадзенняў у аплікацыі з стрімінгам у React: Context, Redux Toolkit і RTK Query
Адаптавайце фронтэнд для стрімінгу відеа, пачынаючы з проп-драйвінгу, пераходзячы через вкладаныя прадастчыкі, дахоўваючыся на слайсах Redux Toolkit і RTK Query, і дазнаецеся, які тип стану паслужыць для кожнага з іх.
Практычна кожны ўзрастаючы React-прыемкі доходзіць да таго стану, калі корневы компонент захопліваецца шаром прадастэров, а таймер відтворення яким-та чынам занова атрыбутуе компонент спавящага дзвонка. Рашэння рэдкады ёсць адзіна бібліятэка; гэта ўсвядомленне, што разныя виды стану патрабуюць разных месца для сваёй рэалізацыі. У гэтым кераванні як прыклад выкарыстоўваецца фронтэнд для стрімінгу відеа, пры чым рассматрываецца пераход ад использовання пропаў через Context API да Redux Toolkit і RTK Query, а ў канцы даўаецца практычны прынцып для выбору між імі.
Прыклад: фронтэнд для стрімінгу
Уявіце сабе основныя функцыі сервіса для стрімінгу анімэ чы відеа:
- вход у систему і рівень падпіску — бесплатны чы преміум
- спіс для перагляду і рядок «Продыржыць перагляд»
- стан плеера: текущая серыя, прагрэс відтворення і настройкі якосці
- перагляд каталогу і пошук з фільтрамі жанраў
Кожны з іх павінен знаходзіцца гдзе-небудзь у дрэве компанентаў, а калькі компанентаў, якія знаходзяцца далека адзін ад другога, павінны іх чытаць. Самэ гэтая супавесць ёсць тым моментам, калі ранніяя рашэння ўзгляду стану або прыносяць плоды, або за месцы ператвараюцца на повольныя, дорогія процесы переработкі.
Першы этап: праходжэнне праз параметры
Першы інстынкт — перадаць стан найбліжэйшаму спакойнаму предку і прынясці яго далей. Для маленькіх прыладоў гэта правильны падход. Аднак у інтарфейсе стрімінгу шлях ад корана да кнопкі можа быць дужа дзяўгім:
App → MainLayout → ContentSection → AnimeGrid → AnimeCard → PlayButton
Падзеймо, што PlayButton павінен знать ранг прыявлення, каб адначыце вярнуцца рашэнне пра тое, чы глядзець айкон блокавання прэміум-версіі. Штабель прыявлення павінен быть пераданы через MainLayout, ContentSection і AnimeGrid, пры чым ніхто з іх яго не выкарыстоўвае. Яны існуюць у гэтым ланцугу толькі як перадавачы.
Адзін проп, які вырваўся падчас перадачы, ўжо толькі таго лямуе. Проблемы пачынаюцца, калі другі, несувязаны элемент, напрыклад спіс для стежэння, таксама павінен пройсці тым жа маршрутом. Кожны проміжны компонент тады перавозіць пропы, якіх ён не розумее, што ускладняе ўсуненне іх у іншыя частыні коду, тэставанне ў ізоляцыі та аналіз. Перш чым вярнуцца да бібліятэк, варта прачытаць чаму сама проблема з пропамі не є праблемай для інсталляцыі Redux чы Зустанд; композіцыя часта скорачае такі ланцюгі. У гэтым дапрынты, протыяжнасць, даныя справды є глобальнымі.
Другі этап: Контэкст і піраміда прадастаўца
API Context у React — гэта наступны логічны крок. Вы ствараеце AuthContext, абгортаеце весь дрэв у AuthProvider, і PlayButton чытае неабяжлівую інфармацыю безпосередна за дапамогай useContext(AuthContext). Тады не патрабуецца додатковых перавярочак.
Потым з’яўляюцца іншыя глобальныя проблемы, кожная з якіх мае свой прадастальнік, і структура корана прыходзіць да такога віда:
<AuthProvider>
<SubscriptionProvider>
<WatchlistProvider>
<PlayerProvider>
<NotificationProvider>
<ThemeProvider>
<App />
</ThemeProvider>
</NotificationProvider>
</PlayerProvider>
</WatchlistProvider>
</SubscriptionProvider>
</AuthProvider>
Гэта тое, што разработчыкі называюць «пяклам прадастальнікаў»: коран прыложэння стае сумкай вялізных абгортакоў, кожны з якіх даўае ўтрымку. Візуальны шум — гэта найменшая з проблем.
Для дыбаггінгу патрабуецца карта
Калі адыграванне працюе некоректна, спачатку трэба з’ясавіць, які прадастальнік керуе тым станам, а потым праследаваць структуру абгортакоў, каб знайсці месца, дзе він зменяецца. Сам дрэв не дае такой інфармацыі.
Кожная апдэйтаванне доходзіць да кожнага корыстувальніка
Змена значэння контексту прыводзіць да павтоваго атрыбутавання ўсіх компонентаў, якія выкарыстоўваюць гэты контекст, нават тых, якія викорыстоўваюць толькі частку з яго. Для продазвальнага стану неабходна кожныя калікс секунд зберагаць playbackProgress. Якщо гэтае значэнне знаходзіцца ў PlayerContext разам з настройкай якосці, компонент, який толькі адображае індыкатор якосці, пры тым жа продазвальным цыкле таксама павтовага атрыбутуўваецца.
Парарадакт падаёўчыка становіцься неявным кантрактом
WatchlistProvider патрэбуе ID зайшоўага пользователя з AuthProvider, таму яго неабходна розмістіць унутрошчы яго. Нічога ў JSX не паказвае гэтую залежнасць явна, а пераранжаванне падаёўчыкаў пад час рефакторавання можа спрачыніць збоі ў прыкладнай програме, якія будзе важка адступіць.
Рэалістычны симптам: команда спаўнае размяшчае контэкст гэнерала і контэкст паведамлення, і аналіз паказвае, што апошнія актуалізацыі спрычынаюць павтаральную нарадзіце паведамленняў. Нічога не выглядае зламаным, але React Profiler паказвае значна больш нарадзіця, чым трэба для інтэрфейсу.
Контэкст можна налаштаваць, напрыклад, раздзеляючы швайная змінююцыяся значэнняя ў свой сабстытутны контэкст або мемаізуючы значэнняя прадастароў, але кожны такі спосаб дадае больш прадастароў і большых сложнасцей. У такім случае часта простыяй ёст тэператываны склад.
Троцьі этап: Redux Toolkit slices
Многія команды на гэтым этапе выбіраюць легкіяя склады, такія як Zustand. Redux Toolkit застаецца супэрна выборам, калі у вас ёсць калькі сувязаных фрагментаў стата, вы хочаце відлучвацца ад проблем з памяцю і ценіце едынага прыведзенага да вярнасці джерела інформацыі, а ён таксама усуне большую частку базовага коду, які робіў класычны Redux складным.
Кожная проблема становіцца часткай. Частка гэтаго плеяра мае ў сабе текущую серію, прыбыл у викананні задач і якасць, а таксама визначае функціі-редюсеры для змены серіі і апдэйту прогрэсу. Заўважыце, што функціі-редюсеры, здаецца, безпасна мутуюць state напрэму; Redux Toolkit пад капотам выкарыстоўвае Immer, таму такія прызначэння ствараюць новы, незменны стан:
// playerSlice.js
const playerSlice = createSlice({
name: 'player',
initialState: {
currentEpisode: null,
playbackProgress: 0,
quality: '1080p',
},
reducers: {
setEpisode: (state, action) => {
state.currentEpisode = action.payload;
},
updateProgress: (state, action) => {
state.playbackProgress = action.payload;
},
},
});
Няма болей занятка элементаў. Адна-едынственная елемент <Provider store={store}> абгружае всю аплікацыю, а компаненты чытаюць толькі тое, што ім патрэбна, за дапамою useSelector. Пакалі useSelector парабягае выбраную значэнне между кожным перыядам абгружання, элемент PlayButton, який выбірае ранг падпіску, перабягае толькі тады, калі ранг змінюецца, а не ў кожны момент, калі змянюецца прыбылак працы. Такі выбранычны модэль падпіску усуняе большасць непатрэбных перабеганняў, якія стваралася ў версіі з Context.
Іншая значныя перавага — це Redux DevTools. Можна практычна працаваць з кожным адправленым дзеянням, такім як нажатыя кнопка «працаваць», адкорэгуванне прагрэсу чыста зміна эпізода, і бачыць абсолютна, як эвалююцца стан, што значна спрыяе діагназаванню бягаў падчас відтварання, у працоўны спосаб замест таго, каб стежыць за значэннямы через піраміду прадаўчыкаў.
Чэтырый этап: RTK Query для стану сервера
Значная частка складнасці прыложэння ўзагалі не стосуецца стану UI. Цэлькам гэта стан сервера: каталог, рызультаты пошуку, дакладныя відомасці эпізодаў і список для аблікування, якія зберагаюцца на бэкендзе. Традыцыйны падход выкарыстоўвае useEffect разам з калькамі useState за кожны запит, каб ручна стежыць за дадзеннямі, процэсам завантажэння і падазрамі, і гэта часта прыводзіць да супернагонкі і дубліравання запытоў.
RTK Query заменяе яго фрагментам API. У наведзаным выкладку ваказана базовая URL і адказаны два канцэнтры запитоў: адны для анімэ, фільтруванага па жанре, і другі для дакладнасцей эпізодаў:
export const catalogApi = createApi({
reducerPath: 'catalogApi',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (builder) => ({
getAnimeList: builder.query({
query: (genre) => `/anime?genre=${genre}`,
}),
getEpisodeDetails: builder.query({
query: (episodeId) => `/episodes/${episodeId}`,
}),
}),
});
RTK Query стварае хук React для кожнага канцэнтра, які называецца так сама, і які вы экспортуе з фрагмента:
export const { useGetAnimeListQuery, useGetEpisodeDetailsQuery } = catalogApi;
Потым компонент атрымляе даны, статус завантажэння і статус памялкі ў адной лініі:
const { data: animeList, isLoading, error } = useGetAnimeListQuery('action');
Няма ручна напісанага эфекту і няма ручнага флажка завантажэння. Выдатная асоблівасць — кэшаванне. Якщо корыстнік ачынае раздзел з жанрамі, перайде іншым курсам і вернуцца, кэшаваныя даны паказваюцца адразу, а RTK Query перзавантажвае іх у фоне, якщо даны застарелі. Аднаковыя запиты з калькольных компонентаў дзелюцца аднам сеансам сеті.
Для рэжыму продажнага перагледу, выкарыстоўванне тэгаў для анулявання дапамага падтрымваць точнасць інтэрфейса: запыты адзначаюць, што яны надаюць, за дапамогою providesTags, а мутацыі — што яны зменяюць, за дапамогою invalidatesTags. Калі запускаецца мутацыя для апдэйта стану, RTK Query автаматычна падзяляе зноў афектаваныя запыты, таму жадны компонент не патрабуе ручнага падзялення. Для тэгаў на сектары API неабходны элемент tagTypes і канцэнтры мутацый, якіх няма ў вышэўказанам фрагменте; наша інструкцыя па адправцы дадзэнняў за дапамогою мутацый RTK Query паказвае гэты аспект. Таксама памятайце, што редюсер і мідлвэрк сектара API павінны быць даданыя ў стоар, каб кэшаванне працавала.
Спадзяраванне кожнага віда стану з адпаведным інструментам
Для такога дапыту разумны падзел выглядае так:
- Локальны ўставак з
useStateдля всьога, што ніколі не выходзіць за межы компонента: заполнення форм, переключальні елементы, станы пад наведенням і адкрытым станам. - Context API для простых глобальных значэнняў, якія рэдка зменяюцца, такіх як тэма або мова. Ён не падходзіць, калі існуе многа контэкстоў або значэнняў, якія часта адчынываюцца.
- Redux Toolkit для складных, взаісвяжаных станоў кліента, такіх як аутэнтыкацыя, ранг падпіску, стан плеўчыка і список для старання, якія чытаюцца і запішываюцца многами несвязанымі компонентамі.
- RTK Query для всьога, што прыходзіць з бэкенду, чым усунулася цэлая група багоў: застарелыя даны, супернічання запытоў і зайвыя запыты.
Звычныя памылкі — це калі спрытваюцься рассматраць гэта як выбор «всё альбо нічога», ставячы всё ў Context або весь час у Redux. Шэрыя з’ёмнікі рашаюць разныя проблемы, і зрэлыя дапрынты зазвычай ўжоўваюць іх разам.
Ключовыя выводы
- Prop drilling — это сігнал пераструктураваць код спачатку; прыбегаюць да глобальнага стану толькі тады, калі даныя дзейсна дзеляюцца между аддаленымі часткамі дапрынты.
- Context перысвечвае кожны элемент, які ім викорыстоўваецца, пасля кожных змян, таму ён не падходзіць для часта змінюючыхся значэнняў, такіх як прагрэс відтворэння.
- Закрытыя залежнасці ў парадку між падаёмымі данымі ёсць рызыкам для тыпавання, який зрастае з кожным новым Context.
- Сабскрыпціяў на адборы даных у Redux Toolkit і інструменты развіцця робяць спакушаны стан кліента як шырэйшым, так і простейшым для дыбагавання.