React to jedna z najpopularniejszych bibliotek JavaScript, często wykorzystywana przy tworzeniu sklepów internetowych Wrocław i innych aplikacji webowych. Dzięki możliwości integracji z Reduxem, zarządzanie stanem staje się bardziej efektywne, szczególnie w rozbudowanych projektach, takich jak tworzenie portali internetowych czy stworzenie platformy sprzedażowej.
Dzięki dużemu społeczeństwu zebranemu wokół biblioteki, rozwiązania na większość napotykanych problemów można znaleźć już gotowe na internecie. Z biblioteki korzysta wiele znanych marek będących częstym miejscem odwiedzin pewnie większości z nas. Są to między innymi Facebook, Instagram, Netflix, AirBnb, Imgur, PayPal czy DropBox.
Głównymi cechami wyróżniającymi bibliotekę React są:
- Wirtualne drzewo DOM, dzięki któremu aplikacja nie musi się przebudowywać cała podczas zmian. Wyszukuje tylko miejsca, które różnią się od poprzedniej wersji i tylko te komponenty są przebudowywane.
- Kolejną cechą jest rozszerzenie języka Javascript zwane JSX, dzięki któremu możemy między innymi używać HTML-owych tagów lub Reaktowych komponentów w JavaScripcie. Czym są komponenty? Są one podstawowym “budulcem” w bibliotece. Ich zadaniem jest dzielenie tworzonej strony na mniejsze reużywalne komponenty.
- React posiada jeszcze wiele innych udogodnień. Jeżeli zaciekawiłem Cię tymi informacjami, polecam zapoznać się z oficjalną dokumentacją aplikacji. Dostępna jest jej polska wersja (https://pl.reactjs.org/). Rzuć także okiem na nasz inny wpis, gdzie poruszany jest temat pisania bloga we frameworku Gatsby bazującym na bibliotece React.
Dzięki swoim cechom, React często wykorzystywany jest do pisania aplikacji SPA, czyli single page application. Oznacza to, że strona posiada jeden plik HTML, i nie jest przeładowywana podczas przeglądania.
Jednokierunkowy przepływ danych
Przepływ danych w bibliotece możemy opisać w sposób następujący: stan aplikacji/komponentu jest przesyłany do widoku, który jest tworzony na jego podstawie. Na widoku możemy rejestrować akcje zmieniające ten stan, na przykład kliknięcie przycisku. Na podstawie zmienionego stanu przebudowywany jest widok komponentu, na przykład licznika oraz komponentów jemu zależnych (“dzieci”). Mamy tutaj swoiste koło zależności. Przepływ danych w bibliotece React jest jednokierunkowy (ang. Unidirectional Data Flow) oznacza to tyle, iż dane płyną w jednym kierunku od rodzica w dół do dzieci. Dane te nazywamy propsami. Komponenty dzieci nie są w stanie wpływać na komponent rodzica. Za takim rozwiązaniem idzie szereg zalet takich jak:
- Łatwiejsze debugowanie aplikacji.
- Wiemy, skąd idą dane, więc szybciej możemy znaleźć błędy.
- Mamy dużą kontrolę nad przepływem.
- Powiązania między komponentami są jasno określone.
Dzięki jednokierunkowemu przepływowi danych podczas zmiany state spowodowanej daną akcją, nie cała aplikacja musi być przebudowywana. Wystarczy zmiana w komponencie, w którym wystąpiła akcja oraz jego komponentach dzieciach. Sam zamysł jednokierunkowego przepływu danych pochodzi z architektury flux, również zaproponowanej przez zespół Facebooka (Meta).
Architektura Flux
Sam React nie wymusza na nas sposobu oraz nie nadaje reguł w sposobie pisania architektury naszej aplikacji, co może być uznawane za wadę jak i zaletę. Dla użytkownika oznacza to pełną swobodę w decydowaniu o architekturze, co często może nieść za sobą wiele błędów szczególnie w przypadku programistów z niskim stażem. Natomiast niewątpliwą zaletą takiego rozwiązania jest niski próg wejścia biblioteki, prostą aplikację możemy zacząć pisać niemal bez znajomości Reacta.
Z pomocą tutaj przychodzi architektura Flux, która nie jest nakazywana, a polecana do użytku. Flux powstał w oparciu o wzorzec projektowy zwany CQRS (Command Query Responsibility Segregation), który został opisany przez Bertranda Meyera i polega na rozdzieleniu zapytań od aktualizacji. Oznacza to, że oddziela się segment kodu odpowiedzialny za pobieranie danych od segmentu kodu odpowiedzialnego za modyfikację danych.
W oficjalnej dokumentacji Fluksa możemy znaleźć infografikę dobrze obrazującą założenia Fluksa.
Źródło: https://facebook.github.io/flux/docs/in-depth-overview.
To, co widzimy tutaj, to wcześniej opisany jednokierunkowy przepływ danych:
- Akcje: Aktywowane przez zdarzenia na widoku, wędrują do Dispatchera.
- Dispatcher: Jego zadaniem jest odbieranie akcji i ich dalsze rozprowadzanie do odpowiednich Store’ów.
- Store: Odpowiada za przechowywanie danych aplikacji. Dodatkowo jego wartości mogą być modyfikowane jedynie przez akcje przechodzące przez Dispatcher.
- View: Budowany i uaktualniany na podstawie stanu (danych) ze Store’a. Może zawierać elementy UI pozwalające na wysyłanie akcji np. kliknięcie.
Istnieje wiele różnych implementacji architektury Flux. W przypadku Reakta najpopularniejszym połączeniem jest Redux, jednak możemy znaleźć wiele innych implementacji takich jak na przykład MobX, Vuex, Alt czy Reflux.
Redux – implementacja architektury Flux
Redux jest jedną z najpopularniejszych bibliotek do zarządzania stanem aplikacji. Najczęściej używany jest w parze z Reaktem, co nie znaczy, że jest ograniczony stricte do takiego użytkowania. Może iść w parze z różnymi frameworkami lub nawet z Vanilla Js. Jak już wspominałem powyżej, Redux jest implementacją architektury Flux. Sami twórcy opisują, że zarządzanie stanem aplikacji przy pomocy reduksa nadaje jej przewidywalność i łatwość śledzenia przepływu danych. Jednak coś kosztem czegoś – wiele osób narzeka, że nawet do wykonania prostych operacji należy napisać dużo tak zwanego boilerplate’u, czyli dodatkowego kodu.
W samym Reakcie, Redux – dzięki tworzeniu globalnego store’a – ułatwia przesyłanie stanu między komponentami niebędącymi bezpośrednimi dziećmi. Dodatkowo społeczność biblioteki jest naprawdę spora, więc użytkownik może znaleźć wiele poradników lub rozwiązań napotkanych problemów.
Wiele takich bibliotek traci swoje zalety wraz z rozwojem i powiększaniem się naszej aplikacji. Nie dotyczy to jednak reduksa, gdyż dzięki swoim założeniom architekturalnym i oddzieleniu logiki biznesowej (czym jest logika biznesowa i więcej o projektowaniu aplikacji) od widoków aplikacji, jest przewidywalny dla osoby, która tworzy aplikację jak również dla nowej osoby, która potem będzie ją rozwijać.
Oto, jak przebiega przepływ danych w Reduksie:
- Cały stan używany między komponentami jest przechowywany w jednym miejscu i cała aplikacja ma do niego dostęp.
- Dostęp ten jednak jest jedynie do odczytu, a jego zmiany są możliwe jedynie za pomocą akcji, które są zwykłymi obiektami JS.
- Akcje przechodzą przez reducer, który analizuje je po kolei i decyduje, w jaki sposób ma zmienić stan.
- Reducer zawsze jest czystą funkcją, która przyjmuje poprzedni stan oraz akcję i zwraca nowy stan zamiast mutować
Ważnym założeniem Reduksa jest niemutowalność stanu, a zamiast tego za każdym razem kopiujemy poprzedni stan, zmieniamy go i podmieniamy poprzedni stan, nowym.
Przekładając Reduksa na prosty język:
- Action to prosty obiekt JavaScriptu. Posiada pole typu i często pole payload, za którym kryją się dodatkowe informacje idące z akcją. O akcji można myśleć jako o opisie zdarzenia, jakie stało się w aplikacji:
const addTodoAction = { type: ‘todos/addTodo’, payload: Add Article’ }
- Action creator to funkcja pisana dla ułatwienia, by nie pisać Akcji za każdym razem:
const addTodo = text => { return { type: ‘todos/addTodo’, payload: text }
- Reducer to czysta funkcja otrzymująca stan i akcję. Decyduje, jak stan ma zostać zmieniony i go zwraca. Można by porównać reducer do JS-owego event listenera, który wykonuje operacje na podstawie otrzymanego typu akcji. Powinny zmieniać stan na podstawie otrzymanego stanu i akcji. Nie powinny mutować stanu, tylko kopiować poprzedni i zmieniać kopię oraz nie powinny wykonywać asynchronicznej logiki. Powinny być czystą funkcją, więc zawsze powinny zwracać te same dane przy takim samych argumentach funkcji:
const initialState = {todoList: []} const todoReducer (state = initialState, action) { switch(action.type) { case(‘todos/addTodo’): return { ...state, todoList: [...state.todoList, action.payload] } default state } }
- Store: miejsce, gdzie Redux przetrzymuje stan.
- Dispatch: Wywołanie zdarzenia, które nasłuchuje reducer, dzięki czemu zmieniany jest stan w odpowiedni sposób. Wywołujemy action creatory, czyli wyżej wspomniane funkcje, aby wybrać odpowiednią akcję.
Podsumowując: zdarzenie na widoku aplikacji (np. kliknięcie) “dispatchuje” czyli wysyła akcję, na którą oczekuje reducer, który najczęściej przy pomocy zwykłego JS’owego switcha porównuje typy akcji i przy zgodnym typie zmienia odpowiednio stan aplikacji i aktualizuje store’a. Po więcej informacji zapraszam do zapoznania się z oficjalną dokumentacją biblioteki.
Hooki
W wypadku niewielkich stron lub aplikacji react może być samowystarczalny dzięki pomocy hooków. Nie oznacza to jednak, że nie da się napisać dużej strony w czystym Reakcie bez zewnętrznych frameworków do zarządzania jej stanem. Jest to jednak mało wydajne i mało wygodne w utrzymaniu i rozwoju.
Dzięki hookom (takim jak useState, useContext czy useReducer), jesteśmy w stanie zarządzać stanem aplikacji.
Hook useContext możemy przyrównać do reduksowego store’a, a useReducer już samą nazwą podpowiada nam, że działa na podobnej zasadzie jak ten reduksowy. Więcej na temat hooków znajdziesz w naszym wpisie lub w oficjalnej dokumentacji.
Według mnie wnioskiem, jaki możemy wyciągnąć z istnienia dobrodziejstwa hooków jak i zewnętrznych bibliotek stworzonych do zarządzania stanem, jest to, że powinniśmy ich używać razem, w zależności od danej sytuacji. Od czasu wprowadzenia hooków korzystanie z Reduksa w Reakcie stało się dużo prostsze.
React – Hooki, Redux – kiedy używać, którego?
Częstym błędem osób zaczynających swoją przygodę z Reaktem i Reduksem jest nadużywanie Reduksa.
Dan Abramov - jeden z twórców Reduksa – na swoim blogu pisze:
I would like to amend this: don't use Redux until you have problems with vanilla React.
Przy decyzji, czy w danym wypadku powinieneś użyć hooków czy Reduksa, może pomóc Ci odpowiedź na takie pytania jak:
- Czy będę potrzebował tych danych w całej aplikacji czy tylko w tym komponencie?
- Czy przekazuję ten state tylko do najbliższego dziecka czy będę go potrzebował głębiej?
- Czy w przyszłości będę potrzebował zapisać gdzieś te dane?
- Czy ten state będzie potrzebny po przeładowaniu strony (Redux zapisuje dane w localStorage)?
- Czy logika aplikacji staje się zbyt skomplikowana, aby pracować na samych hookach?
- Czy dane z formularza to jednorazowy strzał czy będę później na nich pracował?
- Czy nad aplikacją będzie pracować wiele ludzi, którym łatwiej będzie pogodzić prace na stanie globalnym?
Powyższe pytania nie zdecydują za Ciebie, co powinieneś użyć, ale odpowiedź na nie może pomóc Ci w wyborze.
Redux toolkit
Najnowszym sposobem korzystania z Reduksa, proponowanym przez samych twórców jest Redux Toolkit, który znacznie upraszcza korzystanie z biblioteki początkującym. Natomiast osobom zaznajomionym z funkcjonalnościami daje nowe możliwości i przyspiesza korzystanie z wcześniej istniejących rozwiązań. Dodatkowo znacznie ogranicza cały boilerplate, który był podawany za główną wadę paczki. Polecam zapoznanie się z oryginalną dokumentacją znajdującą się na stronie Getting Started | Redux Toolkit.