Наскоро започнахме да използваме SpeedCurve за проследяване на ефективността на Unsplash. За да накараме неговото „Наблюдение на реални потребители“ (известно още като „LUX“) да работи с нашето приложение, трябваше да свършим допълнителна работа, за да управляваме навигациите от страна на клиента. Понастоящем „документацията“ по въпроса е доста оскъдна, така че ето (подробно) ръководство за това как да го направите с помощта на Redux.
ЗАБЕЛЕЖКА: с изключение на малка стъпка, свързана с React, това ръководство е до голяма степен агностично за рамката, стига да имате Redux.
Проблем
Ето последователността от събития, с които работим (според документите на SpeedCurve):
- потребителят извършва навигация в приложението, което води до промяна на URL адреса
- звъним на
LUX.init()
- данните се извличат за новия маршрут (по избор)
- изобразява се нов маршрут
- ние се обаждаме на
LUX.send()
Ще изберем да използваме „redux-observable“, тъй като това ще улесни слушането на потоци от действия и съответно извършването на действия.
redux-observable
По-голямата част от нашата работа ще бъде в рамките на redux-observable
„епос“. Епосите имат следния типов подпис:
type Epic = (action$: Observable<Action>, state$: Observable<State>) => Observable<Action>
Това означава, че те получават поток от редукционни действия и поток от редукционно състояние и извеждат поток от редукционни действия. За повече информация прочетете техните документи.
ЗАБЕЛЕЖКА:в това ръководство ще обясня накратко какво прави всеки оператор Observable. Въпреки това, ако сте нов в Observables, вероятно ще се възползвате от отварянето на тези ресурси за справка:
- https://www.learnrxjs.io/
- https://rxmarbles.com/
- https://rxjs-dev.firebaseapp.com/api?query=operators&type=function
URL промени
Трябва да започнем, като идентифицираме кога URL адресът се променя, което показва, че потребителят е навигирал към нов маршрут.
В нашия конкретен случай използваме react-router с connected-react-router, което ни позволява да четем състоянието react-router
от Redux, което улеснява идентифицирането на URL промените. (Ако не използвате тези библиотеки, пак ще се оправите, стига да съхранявате името на пътя си в Redux или да имате някакъв вид имена на пътища, излъчващи за наблюдение)
- Използвайки
state$
, ние четем потока от редуцирано състояние. Всеки път, когато състоянието се промени, се излъчва нова стойност, която ще бъде манипулирана от всички оператори в рамките на повикването.pipe()
. map
иfilter
се държат на наблюдаеми по начина, по който техните двойни масиви се държат на масиви.
След това завършваме с pathname$
: наблюдаем, който излъчва всички валидни pathname
стойности. И накрая, distinctUntilChanged()
ще излъчва нова стойност само когато е различна от предишната. Това е необходимо, защото location$
може да излъчва нови стойности, дори ако location.pathname
не се е променило.
Сега имаме newPathname$
, поток от различни имена на пътеки, всеки от които представлява нова навигация от страна на клиента. След това можем да извикаме LUX.init
след всеки един:
(tap
е оператор, който използвате, когато искате да извършите странични ефекти.)
Извличане и изобразяване на данни
Сега за трудната част. Нашите маршрути могат да бъдат групирани в две:
- тези, които изискват извличане на данни преди изобразяване („динамични“ маршрути)
- тези, които не се нуждаят от данни и могат да рендират незабавно („статични“ маршрути)
Трябва да моделираме тези сценарии в Redux. Ще направим това, като използваме три действия: ROUTE_DATA_FETCHED
, DYNAMIC_ROUTE_COMPONENT_UPDATED
и STATIC_ROUTE_COMPONENT_UPDATED
. (Това е единственият бит, специфичен за React)
PS: За удобство написахме trackRouteUpdates
HOC, който изпраща componentDidUpdate
действията, и обвихме всички наши компоненти на маршрута с него. Силно препоръчваме да направите същото, за да избегнете повтарянето на тази логика във всеки компонент.
Сега, след като имаме нашите действия, нека се върнем към нашия епос и да ги използваме:
Действията вътре в componentDidUpdate
трябва да бъдат изпратени много пъти, но ние се занимаваме само с първото. takeOneAction
ще слуша за конкретно действие и ще завършинаблюдаваното, след като действието бъде намерено веднъж в потока action$
(когато едно наблюдаемо е „завършено“, то спира да излъчва стойности).
Нека съберем тези три наблюдаеми за действие заедно:
За динамични маршрути трябва да изчакаме данните да бъдат извлечени и след товамаршрутът да се актуализира в този ред. Това прави concat
, докато takeLast(1)
грабва последната стойност, излъчена от свързания поток. Това е удобен начин да разберете, че и двете вътрешни наблюдаеми са завършени.
Край (почти)
Сега можем да свържем това с newPathname$
наблюдаемото, което написахме по-рано:
(Забележете как горният код перфектно отразява последователността от събития, описани в началото на статията).
mergeMap
ще съпостави вашето newPathname$
наблюдаемо към вътрешното наблюдаемо действие. Вариантът mergeMapTo
се използва, когато не се нуждаете от стойностите на предишната наблюдаема, какъвто е случаят тук (стойностите newPathname
не са необходими в waitForDynamicOrStaticRouteUpdated$
).
Обработка в очакване на LUX проследяване
Свършихме!...е, не съвсем. Едно предупреждение е случаят, когато потребителят бързо навигира от страница A към B към C, преди страница B да завърши изобразяването. В крайна сметка ще извикаме LUX.init
два пъти подред: веднъж за страница B и отново за страница C.
Това не може да се случи: всяко LUX.init()
трябва да бъде съпоставено с LUX.send()
. Решихме да поправим това, като се уверим, че LUX.send
винаги се извиква, преди да направим ново повикване LUX.init
.
Не особено харесваме това решение (това е една от само няколко променливи променливи в цялата ни кодова база). Въпреки това, решаването на това по неизменен „RxJS-way“ се оказа толкова сложно, че в крайна сметка бяхме доволни от горното. Ако имате по-чист начин да го направите, моля, уведомете ни!
Краен резултат ✨
Най-накрая сме готови. 🤯
Ако харесвате това, което четете и смятате, че ще ви хареса да работите с нас, ние наемаме! https://unsplash.com/hiring/job-posts/4/react-engineer