22.12.2022, edited 22.12.2022

Czy redux-saga jedt dobrym wyborem w 2023?

ᛋᚬᚷᚨ

Wstęp

Pierwsza wersja redux’a została wydana w 2015 roku i szybko stała się de facto standardem do zarządzania stanem w projektach reactowych. Złożyło się na to kilka powodów, ale z mojej perspektywy najważniejszymi były:

  • brak wygodnego (i wydajnego) narzędzia do propagowania stanu do głęboko zagnieżdżonych komponentów - API kontekstu było niestabilne w tamtym czasie, a hooki (w szczególności useReducer) zostały wprowadzone znacznie później
  • możliwość podpięcia middleware’ów, co pozwala na obsługę wszelkiego rodzaju cross-cutting-concerns i side effect’ów poprzez przechwytywanie akcji przed, a nawet po wywołaniu reducera, co oznacza, że jest to coś więcej niż ‘tylko’ implementacja wzorca chain of responsibility, który jest powszechnie stosowany w serwerach webowych (np. w middleware’ach w node.js, filtrach w java ee etc)

Gdy to piszę, mamy prawie 2023 rok i wydaje się, że zarówno API kontekstu, jak i hooki stały się domyślnym wyborem dla większości projektów reactowych. Jest tak dlatego, że razem rozwiązują problem propagowania stanu do (głęboko) zagnieżdżonych komponentów. Co jednak z funkcjonalnością middleware’ów - czy jest ona pokryta przez wbudowane funkcje reacta?

O ile mi wiadomo - nie - i jest na to dobre wytłumaczenie: w react’cie komponent jest centralnym elementem, więc jeśli trzeba wykonać jakieś side effect’y, to dzieje się to bezpośrednio w nim, poprzez hooki (w komponentach funkcyjnych) lub metody life-cycle (w komponentach klasowych).

Dla większości aplikacji jest to wystarczające, ale moim celem w tym poście jest pokazanie sytuacji, w których nadal warto rozważyć wybór redux’a z redux-sagą (lub podobnym middleware’em).

Czym jest redux-saga

Redux-saga to jeden z najpopularniejszych middleware’ów do zarządzania side effect’ami w ekosystemie redux - według npm-trends ma ona około 1 miliona pobrań dziennie. Dla porównania: redux-thunk ma około 2 razy więcej, a redux-observable 5 razy mniej. Muszę jednak przyznać, że osiągnęła ten poziom w 2021 roku i w zasadzie tak zostało. Mam zresztą kilka pomysłów, dlaczego tak jest, ale podzielę się nimi dopiero w podsumowaniu.

Koncepcja tego projektu jest ściśle związana z jego nazwą - saga to powszechnie używany wzorzec do orkiestracji obsługi zdarzeń, podobny do process-manager’a. W skrócie, chodzi o grupowanie sekwencji powiązanych zdarzeń w byt, który jest odpowiedzialny za koordynację całego procesu krok po kroku i ewentualną obsługę błędów, jeśli takowe wystąpią 1. Myślę, że nazwa jest więc dość trafna, ponieważ biblioteka dostarcza potężne API do nasłuchiwania i koordynacji zdarzeń (akcji) - tak jak we wzorcu saga.

Ale dość tej archeologii, spójrzmy, co konkretnie biblioteka oferuje i czy warto ją wybrać.

Do czego saga się nada

Ogólnie rzecz biorąc: do orkiestracji wszelkiego rodzaju side effect’ów, zwłaszcza tych async. Ale żeby być bardziej precyzyjnym, wymienię kilka konkretnych zastosowań:

  • zarządzanie wieloetapowymi procesami - pomyśl o formularzach podzielonych na wiele kroków, np: koszykdostawapłatnośćstatus
  • integracja z różnego rodzaju zewnętrznymi API (pluginy, biblioteki itp), np: odtwarzacze audio/wideo, mapy, wizualizacje, czaty itp. Jest to możliwe, ponieważ oprócz nasłuchiwania na zdarzenia redux’a, saga ma nożliwość integracji innych źródeł zdarzeń poprzez funkcjonalność kanałów (channels).
  • uruchamianie procesów w tle do obsługi takich rzeczy jak usuwanie starych danych, pobieranie aktualnych danych, wysyłanie heartbeat’ów, odświeżanie tokenów itp. Jest to szczególnie przydatne dla aplikacji SPA i react native, bo mają one dłuższy cykl życia (są używane dłużej niż klasyczne aplikacje webowe, często w tle).
  • poprawa wydajności, poprzez dostarczenie narzędzia do wywoływania czegoś bez dotykania stanu (bez renderów!) - można nawet pominąć wywołanie samej akcji redux’a, aby uniknąć wywołania reducer’ów, korzystając z kanałów i wywołać aktualizację stanu (puścić akcję) po wykonaniu operacji zdefiniowanych w sadze. Przydatne np. przy dużych wolumenach zdarzeń generowanych przez interakcję użytkownika, takich jak przewijanie, usuwanie tekstu itp.

Warto tu wspomnieć, że kod stworzony z pomocą sagi jest czytelny ze względu na swoją imperatywną naturę i brak zaciemniaczy w rodzaju reaktywnych strumieni (observable). Można by się kłócić, że reaktywne strumienie są też czytelne, jeśli ktoś potrafi programować w stylu funkcyjnym. Możliwe, ale z mojego doświadczenia to zły pomysł zakładać, że każdy członek zespołu jest (lub będzie) biegły w tym stylu programowania. Być może napiszę o tym posta w przyszłości.

Na koniec jeszcze jedno przemyślenie: dzięki elastyczności, o której już wspomniałem, biblioteka pozwala na naśladowanie różnych modeli obsługi asynchronicznej, takich jak: pub-sub (wiadomix), kanały, structured-concurrency czy nawet model aktorów, bez wprowadzania dodatkowych narzędzi.

Czy warto?

Mam nadzieję, że udało mi się udowodnić, że redux-saga nadal może być użyteczna.
Ale czy jest to jedyna biblioteka zdolna do wykonywania tego rodzaju zadań? Szczerą odpowiedzią byłoby: nie. Pracowałem z sagą przez długi czas i uważam, że jest to naprawdę potężna biblioteka, ale, o ile mi wiadomo, większość zastosowań, które opisałem w poprzednim punkcie, może być również pokryta przez nową, wbudowaną funkcję w redux toolkit - listener. Co więcej, jeśli ktoś chce spróbować zdecentralizowanego zarządzania stanem, może warto spróbować x-state - biblioteki implementującej wzorzec finite-state-machine. Według npm-trends, w ostatnim czasie zyskała ona na popularności i podejrzewam, że jest to jedna z przyczyn stagnacji redux-sagi. Ciekawostka: jeden z członków core team’u x-state był maintainer’em sagi.

Podsumowując, uważam, że redux-saga nadal jest bardzo dobrą biblioteką do wypróbowania, ponieważ nawet jeśli nie jest (już) najgorętszą technologią, to nadal ma do zaoferowania eleganckie, proste2 i elastyczne API, które może pomóc w rozwijaniu umiejętności i zrozumienia zaawansowanych technik obsługi asynchroności.


Footnotes

  1. Zwane akcją kompensacyjną i służy do wycofywania (rollback). W swojej istocie działa to jak contra entry lub storno w branży finansowej

  2. pamiętaj: proste != łatwe