Co to jest Redux?

Redux to open-sourcowa biblioteka JavaScript do zarządzania stanem aplikacji za pomocą prostego, ograniczonego interfejsu API zaprojektowanego jako przewidywalny kontener dla stanu aplikacji. Działa podobnie do funkcji redukującej, koncepcji programistycznej.

Ludzie używają Reduxa od lat. Redux był rewolucyjną technologią w ekosystemie React. Umożliwił on nam stworzenie globalnej “przechowalni” z niemutowalnymi danymi i naprawiło problem prop drillingu w naszym drzewie komponentów. Wciąż jest doskonałym narzędziem do dzielenia niemutowalnych danych w aplikacji, które dodatkowo dobrze się skaluje. Redux pomaga pisać aplikacje, które zachowują się spójnie, działają w różnych środowiskach (klient, serwer i native) i są łatwe do testowania.

Dlaczego w takim razie nie powinienem używać Reduxa?

Przy tych wszystkich wielkich zaletach możesz pomyśleć, dlaczego nie mielibyśmy korzystać z Reduxa?
Jest on często przerostem formy nad treścią i zawiera ogromną ilość zbędnego kodu (boilerplate).
Głównym problemem, z którym się borykamy kiedy używamy Reduxa i podobnych bibliotek do zarządzania stanem, to traktowanie go jako cache dla naszego stanu z backendu.

Fetchujemy dane, dodajemy do naszego stora z reducerem i akcją, a potem co jakiś czas wykonujemy refetch, mając nadzieje że jesteśmy na bieżąco. Za bardzo polegamy na Reduxie i używamy go jako uniwersalnego rozwiązania wszystkich naszych problemów.
Kiedy tworzysz mniejszą aplikację, nie najgorszym pomysłem jest całkowite zrezygnowanie z biblioteki Redux.

Często wymagana jest duża ilość kodu, aby zarządzanie stanem było przejrzyste, spójne i rozszerzalne. Dodatkowo próg wejścia w Reduxie jest dość wysoki, a zrozumienie, jak działają akcje, reductory i dispatche, może być dość przytłaczające dla początkujących.

Świetne funkcje wiążą się z dużą złożonością, która może być niepotrzebnym obciążeniem dla twojego projektu, zwłaszcza jeśli jest nieduży. 

Kiedy warto używać Reduxa?

Nadal istnieją pewne okoliczności, w których aplikacja może rzeczywiście mieć ogromną ilość synchronicznych stanów tylko dla klienta (takich jak aplikacja do wizualizacji lub aplikacja do produkcji muzyki), w takim przypadku prawdopodobnie nadal będziesz potrzebować menedżera stanu klienta.

Powinieneś używać React Redux szczególnie, jeśli interfejs użytkownika jest często aktualizowany, gdy wiele komponentów musi reagować na jedną akcję, a same akcje są bardzo skomplikowane – wtedy dobrze jest użyć Reduxa. Wiele firm wciąż traktuje bibliotekę Redux jako obowiązkową technologię, którą każdy deweloper powinien rozumieć – jest to bardzo widoczne, szczególnie w wymaganiach rynku pracy. 

Czego użyć zamiast Reduxa?

React Query

Pierwszą, bardzo wszechstronną i elastyczną biblioteką do zarządzania stanem, która przychodzi mi do głowy, jest React Query.

Aby łatwo było zauważyć różnice między React Query, a Reduxem, przedstawię zasady działania obydwu, w kodzie. Zaimplementowałem proste TODO pobierane z serwera obydwiema metodami, używając czystego JavaScripta, Hooki Reactowe i Axios. Warto również zauważyć, że Redux i React Query mogą być używane jednocześnie, jedna biblioteka nie wyklucza drugiej.

 Na początek implementacja Redux:

Zauważ, że powyższy kod nawet nie obsługuje ponownego fetchowania, cachowania czy inwalidacji. Po prostu ładuje dane i przechowuje je w globalnym storze przy załadowaniu komponentu. Jeśli chciałbyś zarządzać danymi poprzez cache, to jest przed Toba jeszcze sporo pracy. Nie muszę mówić, że pojawi się jeszcze więcej boilerplate’u.

 A tutaj ten sam przykład, już z wykorzystaniem React Query:

O wiele lepiej. 

 Domyślnie, ten przykład obejmuje ponowny fetching, caching i inwalidację z całkiem rozsądnymi wartościami domyślnymi. Można również ustawić konfigurację cachingu na poziomie globalnym i kompletnie o niej zapomnieć – w większości przypadków zachowa się tak, jak chcesz. Więcej informacji o tym, jak to działa pod maską, znajdziesz w dokumentacji React Query. Dostępnych jest mnóstwo opcji konfiguracyjnych, co tylko zarysowywuje potencjał tej biblioteki.

 Wszędzie, gdzie będziesz potrzebować tych danych, możesz teraz użyć hooka useQuery z ustawionym unikalnym kluczem (w tym przypadku “todos”) i wywołaniem asynchronicznym używanym do pobrania danych. Jeśli funkcja jest asynchroniczna, implementacja nie ma znaczenia — równie łatwo możesz użyć Fetch API zamiast Axios.

Aby zmienić stan naszego backendu, React Query udostępnia hook useMutation.

 W przypadku większości aplikacji prawdziwie globalnie dostępny stan klienta, który pozostaje po migracji całego kodu asynchronicznego z Reduxa do React Query, jest zwykle bardzo mały.

Unstated

Tutaj natomiast znajduje się przykład zarządzania stanem przy użyciu biblioteki Unstated:

Z perspektywy przejrzystości kodu, jest to świetna biblioteka. Używa ona Reactowe hooki dla całej swojej logiki zarządzania stanem.

Jeśli używasz TypeScript (do czego gorąco zachęcam, jeśli jeszcze nie spróbowałeś), ma to również tę zaletę, że wbudowane wnioskowanie TypeScript działa lepiej. Dopóki Twój niestandardowy hook jest otypowany, wszystko inne po prostu będzie działać.

 Ale jakie są prawdziwe zalety używania Unstated, które przebijają Reduxa?

  • Jest mniejszy Jest ok. 40x mniejszy.
  • Jest szybszy. Można “skomponentować” problem wydajności.
  • Jest prostszy do nauki. Od początku będziesz musiał znać Hooki Reactowe & Context, używaj ich, są naprawde świetne.
  • Jest prostszy pod kątem integracji. Integruj jeden komponent na raz oraz łatwo integruj z każdą biblioteką Reactową.
  • Jest łatwiej testowalny. Testowanie reducerów to strata Twojego czasu, ułatw sobie testowanie swoich komponentów.
  • Jest łatwiej sprawdzać typy. Zaprojektowany tak, aby wyciągnąć wszystkie zalety typowania.
  • Jest minimalistyczny. To po prostu React.

Redux Toolkit

Mimo, że nie jest oddzielną technologią, a jedynie dodatkową biblioteką do Reduxa, Redux Toolkit to zestaw narzędzi do wydajnego tworzenia Redux. Ma to być standardowy sposób pisania logiki Redux, a twórcy Redux zdecydowanie zalecają jego używanie.

Zawiera kilka funkcji użytkowych, które upraszczają najczęstsze przypadki użycia Reduxa, w tym konfigurację stora, definiowanie reductorów, niezmienną logikę aktualizacji, a nawet tworzenie całych „plastrów” stanu na raz, bez ręcznego pisania jakichkolwiek creatorów lub typów akcji. Zawiera również najczęściej używane dodatki Redux, takie jak Redux Thunk do logiki asynchronicznej i Reselect do pisania funkcji selektora, dzięki czemu można z nich korzystać od razu.

Redux Toolkit oferuje funkcję configureStore, która zapewnia przydatne wartości domyślne, takie jak standardowe rozszerzenia podczas tworzenia sklepu. Funkcja oczekuje obiektu konfiguracyjnego. Obiekt konfiguracyjny próbuje odwzorować parametry funkcji createStore w sposób bardziej zrozumiały.

Przykład konfiguracji Reduxowego store’a z różnymi reducerami, middleware’ami, obsługą narzędzi deweloperskich, stanem początkowym i enchancerem widać na poniższym przykładzie:

Aby utworzyć akcję w Redux, zwykle potrzebujesz stałej dla typu i ActionCreator, który używa tej stałej. Prowadzi to do większej ilości boilerplate’u. Metoda createAction pakietu Redux Toolkit łączy te dwa kroki w jeden. Oto przykład normalnego ActionCreatora i sposób, w jaki można go utworzyć za pomocą zestawu narzędzi Redux.

Bez Toolkita:

Z Toolkitem:

Ta funkcja pomaga usunąć większość kodu wzorcowego. Jedną wadą jest to, że nie można już określić, jaki format powinien mieć payload. Przy generowanych akcjach payload nie jest zdefiniowany bardziej szczegółowo i musisz sprawdzić reductor, który należy przekazać jako payload.

Jeśli chcesz pójść o krok dalej, akcje dla store’a również mogą być generowane automatycznie. W tym celu Redux Toolkit oferuje metodę createSlice. Metoda oczekuje jako parametrów nazwy slice’a, stanu początkowego i zestawu funkcji reducerów.

Metoda createSlice następnie zwraca zestaw wygenerowanych elementów – ActionCreatorów, które pasują do kluczy w module Reducer. Wygenerowane akcje mogą otrzymać odpowiedni payload i mogą być łatwo wykorzystane. Jednak generowanie tych działań stosunkowo szybko osiąga swoje granice. Nie jest już możliwe mapowanie akcji asynchronicznych, na przykład za pomocą Redux Thunk.
Poniższy przykład pokazuje, jak korzystać z funkcji i wynikowych twórców akcji:

Metoda createSlice może znacznie zredukować wymagany boilerplate dla niektórych store’ów, chociażby dlatego, że nie trzeba zwracać całego stanu, a jedynie wystarczy nadpisać pojedynczą właściwość (property) danego stanu. Nie jest ona jednak zbyt elastyczna i należy sprawdzić, czy nadaje się do konkretnego zastosowania.

Redux Toolkit zawiera szereg funkcji, które mogą uprościć pracę z Reduxem. Domyślnie obejmuje wiele standardowych przypadków, ale nadal można je skonfigurować do bardziej szczegółowych zadań. Pakiet Redux Toolkit zawiera zbiór bibliotek, które są już powszechnie używane i dobrze ze sobą współpracują. Dzięki tym bibliotekom i nowym funkcjom pakietu Redux Toolkit można uniknąć wielu przypadkom boilerplate’u, a składnia staje się nieco jaśniejsza i łatwiejsza do zrozumienia.

A co z cache’owaniem?

Biblioteka, która bardzo dobrze sobie radzi z cache’owaniem to właśnie React Query. Komponenty są trzymane w cache, więc po otrzymaniu danych z serwera, są wyświetlane niemalże natychmiastowo, nawet jeśli odświeżymy DOM. Jest to kluczowe dla każdego projektu, w którym wiele wyświetlanych jednocześnie komponentów, pobieranych jest z serwera. Cache’owanie zapobiega przeładowywaniu każdego elementu, tym samym znacząco poprawiając wydajność i ogólny User Experience.

Właśnie stworzyłem swój nowy projekt – instaluję Reduxa!

Wiele osób uważa Redux za pewnik, kiedy zaczynają pracę nad swoim nowym projektem. Nie zapominaj, że możesz zastosować pomysły z Reduxa bez używania go. Rozważmy na przykład komponent React ze stanem lokalnym:

Czy powinieneś używać tego rozwiązania przy komponentach niemal przepełnionymi stanami? Raczej nie. To znaczy, chyba że masz plan, aby skorzystać z tego dodatkowego pośrednictwa. W dzisiejszych czasach kluczem jest posiadanie planu.

 Biblioteka Redux jest jedynie zestawem narzędzi do “zamontowania” reducerów do globalnego obiektu pojedynczego store’a. Możesz używać tak niewiele lub tak dużo Reduxa, jak tylko chcesz.

Na przykład z biblioteką taką jak React Query możesz pozbyć się:

  • Connectorów
  • ActionCreatorów
  • Middleware’u
  • Reducerów
  • Stanów Loading/Error/Result 
  • Contextów

Po usunięciu wszystkich tych rzeczy możesz zadać sobie pytanie: „Czy warto nadal używać jakiegokolwiek menedżera stanu klienta dla tak malutkiego stanu globalnego?”

Według mnie biblioteka Redux powinna być ostatecznością podczas wybierania rozwiązań zarządzania stanem, jednak decyzja zależy już tylko i wyłącznie od Ciebie!

Phillip Ławniczak