Zbliżamy się do zakończenia pracy nad trackerem kryptowalut. Jedyne, czego brakuje do zrealizowania założeń, to wprowadzenie aktualnych danych o kursach z zewnętrznego serwera. Do realizacji tego celu posłuży nam AJAX oraz API CoinMarketCap. React jest biblioteką skoncentrowana na View, stąd na próżno szukać wbudowanego rozwiązania do obsługi asynchronicznego JSa. Otwiera to przed nami szereg możliwości, co ma tyle samo wad (co wybrać?!), co zalet (to co Ci odpowiada! ;)). Zacznę od omówienia dwóch najpopularniejszych sposobów na AJAX w React czyli natywnego fetch
i biblioteki axios
. Następnie przejdziemy do dopieszczania trackera kryptowalut świeżymi danymi wprost z serwerownii.
Co mają do zaoferowania przeglądarki?
fetch
to następca przestarzałego XMLHttpRequest
(nazwa zdradza jego archaiczne korzenie). fetch
jest oparty o promise’y, co stanowi zdecydowany krok w przód jeżeli chodzi o łatwość obsługi zapytań asynchronicznych. Dostęp do interfejsu uzyskujemy za pomocą globalnej metody fetch()
. Twórcy specyfikacji fetch
postarali się o zachowanie możliwie największej liczby podobieństw z XHR. Dzięki temu będzie wydawał się on znajomy każdemu, kto miał (nie)przyjemność korzystania z poprzednika fetch
. Czy była to trafiona decyzja? Tego dowiemy się w dalszej części wpisu (spoiler: NIE!).
fetch
wymaga jednego argumentu do wykonania zapytania. Jest nim URL zasobu, który chcemy odpytać.
fetch
zwraca promise’a, który wywiązuje się do obiektu Response
, niezależnie od tego, czy zapytanie było pomyślne. To pierwszy problem fetch
. Korzystałem z $.ajax
zaledwie przez rok, a ciężko mi przestawić się na to, że błąd z punktu widzenia programisty (404 lub 500), jest pozytywnym rezultatem dla interfejsu (przecież serwer udzielił odpowiedzi, po co drążyć temat!). W związku z tą pokrętną logiką, reject
jest zarezerowany wyłącznie dla problemów z siecią lub blokadą CORS po stronie serwera.
Zobaczmy, jak wspomniany problem wygląda w praktyce.
Zonk, serwer zwraca 404, ale catch nie przechwytuje błędu, fetch
przechodzi do then, udostępniając nam obiekt Response
.
Musimy rozbudować nasze procedury obsługi, żeby dowiedzieć się, z czym tak naprawdę mamy do czynienia. Sprowadza się to do testowania response.ok
, która jest ustawiana na false, dla kodów statusu spoza zakresu 200-299.
W mniej akademickim przykładzie test response.ok
deleguje się do funkcji, którą zawsze przekazujemy do pierwszego then
.
Dobra, teraz powinno pójść z górki. Suprise! Jeżeli oczekujesz, że jedyne, czego zabrakło w poprzednim przykładzie, to obsługa danych zwróconych przez API, to muszę Cię rozczarować. response
ukrywa jsona, musimy się do niego dobrać przez wywołanie response.json()
.
Jak widzisz, fetch
wymaga naprodukowania kilkunastu linijek kodu dla najbardziej podstawowego przykładu użycia. W każdym zapytaniu będziemy musieli przechodzić przez ten sam proces przygotowawczy.
Jak większość interfejsów AJAX, fetch
opcjonalnie przyjmuje drugi parametr. To obiekt konfigurujący zapytanie. Jeżeli pominiemy ten parametr, fetch
skorzysta z obiektu domyślnego.
Wsparcie fetch
w przeglądarkach nie odbiega od pozostałych funkcjonalności wprowadzonych w ES6. Jeżeli zależy nam na IE, nie obejdzie się bez polyfilla dla promise’ów. W przypadku projektów pisanych w Reactcie nie jest to żadna przeszkodza, i tak korzystamy z rozbudowanej konfiguracji babela i webpacka ;).
Jak pewnie zauważyłeś, nie jestem wielkim fanem fetch
. Nie zmienia to faktu, że gdybym był zmuszony do wyboru natywnego rozwiązania, bez wahania chwyciłbym za fetch
. Na tle XMLHttpRequest
wypada świetnie.
Na szczęście nie jestem zmuszony do takiego wyboru, więc przejdźmy do mojej ulubionej alternatywy.
I <3 open-source
axios
to klient HTTP, który tak jak fetch
, jest oparty o obietnice. Cieszy się niesamowitą popularnością, na co dowodem jest 41,5 tysiąca gwiazdek na GH.
Co sprawia, że programiści tak chętnie korzystają z tej zależności? Nadaje się zarówno do prostych aplikacji w czystym js, programów node.js oraz pracy z frameworkami front-endowymi takimi jak React. axios
oferuje niewiele więcej, niż fetch
. Sęk w tym, że robi to w lepszym stylu. Zdecydowanie bliżej do skojarzeń z $.ajax()
, niż XMLHttpRequest
. Jeżeli chodzi o dodatki, mamy do dyspozycji kilka użytecznych funkcjonalności: stałe konfiguracje, instancje, transformacje, interceptory. O tym za chwilę, najpierw zróbmy praktyczne porównanie axios
z fetch
.
Dwa powyższe warianty przynoszą dokładnie takie same efekty. Różnica w objętości mówi sama za siebie.
Pozbyliśmy się pośredniego kroku z fetch
, mamy natychmiastowy dostęp do jsona. Skorzystałem z destrukturyzacji ({data})
, żeby oszczędzić sobie referowania response.data
. Kolejne usprawnienie to intuicyjna obsługa błędów. Odpowiedź od serwera z kodem statusu spoza zakresu 200-299 jest automatycznie kategoryzowane jako błąd i trafia do catch
.
Jeżeli wcześniej uważałeś, że przesadnie krytykuję fetch
, to teraz wiesz, że to zasługa rozpieszczenia przez axios
a ;).
Aby w pełni uzasadnić obciążanie aplikacji dodatkowymi kilobajtami, przyjrzyjmy się dodatkowym funkcjonalnościom, o których wspominałem wcześniej.
Stała konfiguracja i instancje
W wielu aplikacjach wielokrotnie wykonujemy zapytania pod ten sam url. Tak samo będzie w trackerze kryptowalut, gdzie interesuje nas wyłącznie adres: https://api.coinmarketcap.com/v2/
. axios.defaults
pozwala na ustawienie baseURL
, który będzie używany w wszystkich zapytaniach. W zapytaniach wystarczy dodać interesującą nas ścieżkę.
Działa to na tyle fajnie, że jeżeli przekażemy stringa rozpoczynającego się od ‚http’, to axios
zda sobie sprawę, że rezygnujemy z baseURL
.
Możliwości axios.defaults
nie ograniczają się do url, mamy możliwość ustawienia wartości domyślnej dla dowolnej właściwości konfiguracji (headery, timeout etc.).
A co jeśli mamy dwa (lub więcej) adresów, z których chcemy korzystać zamiennie? Tutaj na pomoc przychodzą instancje.
Transformatory i interceptory
Transformatory pozwalają modyfikować dane żądania/odpowiedzi przed ich wysłaniem/przekazaniem do metod obsługi. Interceptory działają podobnie, ale pozwalają również na modyfikowanie obiektu konfiguracyjnego.
Praktyczne zastosowanie? Modyfikacja konfiguracji w oparciu o dane zwrócone z innej operacji asynchronicznej, wygenerowanie unikalnego tokena i przypisanie go do headera.
Dobrym miejscem na ustawienie interceptora jest componentDidMount()
(jeżeli nie wiesz dlaczego, zerknij tutaj). Musimy pamiętać o przypisaniu go do właściwości komponentu, żeby móc po nim posprzątać w componentWillUnmount()
.
Tracker kryptowalut to prosta aplikacja. Nie będzie potrzeby korzystania z tych funkcjonalności, więc z czystym sumieniem zostawiam Was z przykładem z dokumentacji.
Dawać te dane!
Skoro już zapoznaliśmy się z axios
em, to można zabrać się zastąpienie czterech kryptowalut wklepanych przeze mnie na sztywno do state
czymś bardziej użytecznym. Nasze prace skupią się na komponencie kontenerowym App
. Jako punkt wyjścia potraktujmy ostatni commit z poprzedniego wpisu.
Na początek pozbędziemy się zawartości state.cryptos
, już nie będzie nam potrzebna.
Zanim przejdziemy dalej, przydałaby się instancja axios
powiązana z API CoinMarketCap.
Okej, jesteśmy gotowi na get()
, który odmieni oblicze tej aplikacji. Jeżeli odrobiłeś zadanie domowe z metod cyklu życia, to pewnie słusznie spodziewasz się, że skorzystamy z componentDidMount()
. Wykonam zapytanie do ścieżki, która zwróci 100 najwyżej notowanych kryptowalut.
CoinMarketCap API zwraca zagnieżdżony obiekt data
, którego klucze to ID każdej kryptowaluty. Komponent CoinList
oczekuje tablicy, zbudowałem ją za pomocą Object.keys()
i map
. Wynikiem każdego wywołania callbacka map
jest dodanie do nowej tablicy obiektu kryptowaluty. Dzięki destrukturyzacji ma on taką samą formę co elementy state.cryptos
, z których korzystałem wcześniej.
Natrafiłem na mały problem z ikonami kryptowalut, nie sposób przechowywać lokalnie każdej z nich. Na szczęście mogę skorzystać z zasobów CoinMarketCap. Po krótkiej analizie strony głównej zauważyłem, że ikony są przechowywane pod adresem: https://s2.coinmarketcap.com/static/img/coins/64x64/${id}.png
.
W takim wypadku nie pozostało nic innego jak mała przebudowa metody getIconPath
.
To wszystko czego tak naprawdę nam potrzeba, mamy tracker kryptowalut z prawdziwego zdarzenia. Możesz sprawdzić demo postawione na GitHub Pages, które udało mi się z łatwością postawić dzięki poradnikowi na type-of-web.
Na wersję demo składa się jeszcze kilka commitów, w których:
Twoja kolej
Zostało kilka rzeczy do zrobienia. Tutaj wkraczasz Ty, mój drogi czytelniku. To świetna okazja do przećwiczenia wiedzy, którą dzieliłem się z Tobą w ostatnich dziesięciu wpisach z serii „Pierwszy projekt w ReactJS”.
Lista zadań, z którymi możesz się rozprawić:
- Opracuj obsługę błędów na wypadek gdyby odpowiedź z CoinMarketCap nie dotarła do
crypto-trackera
- Obsłuż aktualizowanie kursów co pięć minut
- Pobierz dane z API o całym kapitale na giełdzie i przekaż je do komponentu
Header
- Obsłuż sytuację, w której użytkownik chce uzyskać dane kryptowaluty spoza top-100
- Posortuj listę kryptowalut według rankingu
Jest co robić. Forkuj repo, czekam na Twojego pull requesta :). Jeżeli ktoś (np. ja ]:->) Cię uprzedzi, to nic straconego, po prostu podziel się swoim rozwiązaniem w komentarzu :).