Наскоро започнахме да използваме 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, вероятно ще се възползвате от отварянето на тези ресурси за справка:

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