Fundamentalne elementy NgRx: Store, Actions, Reducers, Selectors, Effects

Sklep jest kluczowym elementem w całym procesie zarządzania stanem. Przechowuje on stan i ułatwia interakcję pomiędzy komponentami a stanem. Możesz uzyskać referencję do sklepu poprzez Angular dependency injection, jak pokazano poniżej.

constructor(private store: Store<AppState>) {}

Ta referencja do sklepu może być następnie wykorzystana do dwóch podstawowych operacji:

  • Do wysyłania akcji do sklepu poprzez metodę store.dispatch(…), która z kolei będzie wywoływać reduktory i efekty
  • Do pobierania stanu aplikacji poprzez selektory

Struktura drzewa obiektów stanu

Załóżmy, że Twoja aplikacja składa się z dwóch modułów funkcyjnych o nazwach Użytkownik i Produkt. Każdy z tych modułów obsługuje różne części ogólnego stanu. Informacje o produkcie będą zawsze przechowywane w sekcji products w stanie. Informacje o użytkowniku będą zawsze przechowywane w sekcji user stanu. Sekcje te są również nazywane plasterkami.

Akcje

Akcja jest instrukcją, którą wysyłasz do sklepu, opcjonalnie z pewnymi metadanymi (payload). Na podstawie typu akcji, sklep decyduje, jakie operacje wykonać. W kodzie, akcja jest reprezentowana przez zwykły obiekt JavaScript z dwoma głównymi atrybutami, mianowicie type i payload. payload jest opcjonalnym atrybutem, który będzie wykorzystywany przez reduktory do modyfikacji stanu. Poniższy wycinek kodu i rysunek ilustrują tę koncepcję.

{ "type": "Login Action", "payload": { userProfile: user }}

NgRx w wersji 8 udostępnia funkcję użytkową o nazwie createAction do definiowania twórców akcji (nie akcji, lecz twórców akcji). Poniżej znajduje się przykładowy kod do tego celu.

Możesz następnie użyć kreatora akcji login (który jest funkcją) do budowania akcji i wysyłania ich do sklepu, jak pokazano poniżej. user jest obiektem payload, który przekazujemy do akcji.

this.store.dispatch(login({user}));

Reducery

Reducery są odpowiedzialne za modyfikację stanu i zwrócenie nowego obiektu stanu z modyfikacjami. Reduktory przyjmują dwa parametry, aktualny stan oraz akcję. W oparciu o otrzymany typ akcji, reduktory wykonają pewne modyfikacje bieżącego stanu i wyprodukują nowy stan. Koncepcja ta została przedstawiona na poniższym diagramie.

Podobnie do akcji, NgRx udostępnia funkcję użytkową o nazwie createReducer do tworzenia reduktorów. Typowe wywołanie funkcji createReducer wyglądałoby następująco.

Jak widać, przyjmuje ona stan początkowy (stan podczas uruchamiania aplikacji) oraz funkcje zmiany stanu typu jeden do wielu, które definiują sposób reagowania na różne akcje. Każda z tych funkcji zmiany stanu otrzymuje bieżący stan i akcję jako parametry, i zwraca nowy stan.

Efekty

Efekty pozwalają na wykonywanie efektów ubocznych, gdy akcja jest wysyłana do sklepu. Spróbujmy to zrozumieć na przykładzie. Kiedy użytkownik pomyślnie zaloguje się do aplikacji, akcja z type Login Action zostanie wysłana do sklepu z informacjami o użytkowniku w payload. Funkcja reduktora będzie nasłuchiwała tej akcji i modyfikowała stan z informacjami o użytkowniku. Dodatkowo, jako efekt uboczny, chcesz również zapisać informacje o użytkowniku w lokalnym magazynie przeglądarki. Efekt może być użyty do wykonania tego dodatkowego zadania (efekt uboczny).

Istnieje wiele sposobów tworzenia efektów w NgRx. Poniżej znajduje się surowy i samowyjaśniający się sposób tworzenia efektów. Zwróć uwagę, że generalnie nie używasz tej metody do tworzenia efektów. Wziąłem to tylko jako przykład, aby wyjaśnić, co dzieje się za kurtyną.

  • actions$ obserwowalne będą emitować akcje otrzymane przez sklep. Wartości te będą przechodziły przez łańcuch operatorów.
  • ofType jest pierwszym użytym operatorem. Jest to specjalny operator dostarczany przez NgRx (Not RxJS) do filtrowania akcji na podstawie ich typu. W tym przypadku, tylko akcje typu login będą mogły przejść przez resztę łańcucha operatorów.
  • tap jest drugim operatorem używanym w łańcuchu do przechowywania informacji o użytkowniku w lokalnej pamięci masowej przeglądarki. Operator tap jest zwykle używany do wykonywania efektów ubocznych w łańcuchu operatorów.
  • W końcu musimy ręcznie zasubskrybować obserwowalną login$.

Jednakże to podejście ma kilka poważnych wad.

  • Musisz ręcznie zasubskrybować obserwowalną, co nie jest dobrą praktyką. W ten sposób zawsze będziesz musiał ręcznie się wypisać, co prowadzi do braku łatwości utrzymania.
  • Jeśli w łańcuchu operatora wyskoczy błąd, obserwowalny popełni błąd i przestanie emitować kolejne wartości (akcje). W efekcie efekt uboczny nie zostanie wykonany. Dlatego trzeba mieć mechanizm do ręcznego tworzenia nowej instancji obserwowalnej i ponownej subskrypcji, jeśli wystąpi błąd.

Aby przezwyciężyć te problemy, NgRx dostarcza funkcję użytkową o nazwie createEffect do tworzenia efektów. Typowe wywołanie funkcji createEffect wyglądałoby następująco.

Metoda createEffect przyjmuje funkcję, która zwraca obserwowalną i (opcjonalnie) obiekt konfiguracji jako parametry.

NgRx obsługuje subskrypcję obserwowalnej zwracanej przez funkcję pomocniczą, dlatego nie trzeba ręcznie subskrybować ani wypisywać się z niej. Ponadto, jeśli wystąpi jakikolwiek błąd w łańcuchu operatora, NgRx utworzy nową obserwowalną i ponownie zasubskrybuje, aby zapewnić, że efekt uboczny zawsze zostanie wykonany.

Jeśli dispatch jest true (wartość domyślna) w obiekcie konfiguracyjnym, metoda createEffect zwraca Observable<Action>. W przeciwnym przypadku zwraca Observable<Unknown>. Jeśli właściwość dispatch ma wartość true, NgRx zasubskrybuje zwróconą obserwowalność type Observable<Action> i wyśle otrzymane akcje do sklepu.

Jeśli nie mapujesz otrzymanej akcji do innego typu akcji w łańcuchu operatora, będziesz musiał ustawić dispatch na false. W przeciwnym razie wykonanie spowoduje nieskończoną pętlę, ponieważ ta sama akcja będzie wysyłana i odbierana w strumieniu actions$ ponownie i ponownie. Na przykład, nie musisz ustawiać dispatch na false w poniższym kodzie, ponieważ mapujesz oryginalną akcję na inny typ akcji w łańcuchu operatora.

W powyższym scenariuszu,

  • Efekt odbiera akcje type loadAllCourses.
  • Wywoływane jest API, a kursy są ładowane jako efekt uboczny.
  • Odpowiedź API na akcję o wartości type allCoursesLoaded jest mapowana, a załadowane kursy są przekazywane jako payload do akcji.
  • I w końcu, utworzonaallCoursesLoaded akcja jest wysyłana do sklepu. Jest to wykonywane przez NgRx pod maską.
  • Reduktor będzie nasłuchiwał przychodzącej allCoursesLoaded akcji i modyfikował stan za pomocą załadowanych kursów.

Selektory

Selektory są czystymi funkcjami używanymi do uzyskiwania plasterków stanu sklepu. Jak pokazano poniżej, możesz zapytać o stan nawet bez użycia selektorów. Ale to podejście znów ma kilka poważnych wad.

const isLoggedIn$ = this.store.pipe(map(state => !!state.user));
  • store jest obserwowalną, którą można subskrybować. Ilekroć sklep otrzyma akcję, store popchnie obiekt stanu do swoich subskrybentów.
  • Możesz użyć funkcji mapowania, aby uzyskać plasterki stanu i wykonać dowolne obliczenia, jeśli jest to wymagane. W powyższym przykładzie uzyskujemy plaster user w drzewie obiektów stanu i konwertujemy go na boolean, aby określić, czy użytkownik zalogował się, czy nie.
  • Możesz albo ręcznie zasubskrybować obserwowalne isLoggedIn$, albo użyć go w szablonie Angular z rurą async do odczytania emitowanych wartości.

Jednakże to podejście ma poważną wadę. Ogólnie rzecz biorąc, sklep otrzymuje akcje często z różnych części aplikacji. Zgodnie z powyższą implementacją, za każdym razem, gdy sklep otrzyma akcję, obiekt stanu zostanie wyemitowany przez sklep. I ten obiekt stanu ponownie przejdzie przez funkcję odwzorowania i zaktualizuje UI.

Jednakże, jeśli wynik funkcji odwzorowania nie zmienił się od ostatniego razu, nie ma potrzeby ponownego aktualizowania UI. Na przykład, jeśli wynik map(state => !!state.user) nie zmienił się od ostatniego wykonania, nie musimy ponownie przesuwać wyniku do UI/Subskrybenta. Aby to osiągnąć, NgRx (nie RxJS) wprowadził specjalny operator o nazwie select. Dzięki operatorowi select, powyższy kod zmieni się w następujący sposób.

const isLoggedIn$ = this.store.pipe(select(state => !!state.user));

Operator select zapobiegnie wypychaniu wartości do UI/subskrybentów, jeśli wynik funkcji mapującej nie zmienił się od ostatniego razu.

To podejście można jeszcze ulepszyć. Nawet jeśli operator select nie popycha niezmienionych wartości do UI / subskrybentów, nadal musi wziąć obiekt stanu i wykonać obliczenia, aby uzyskać wynik za każdym razem.

Jak już wyjaśniono powyżej, state będzie emitowane przez obserwowalne, gdy sklep otrzyma akcję z aplikacji. Akcja nie zawsze aktualizuje stan. Jeśli stan się nie zmienił, wynik obliczeń funkcji odwzorowującej również się nie zmieni. Dlatego nie musimy wykonywać obliczeń ponownie, jeśli emitowany obiekt state nie zmienił się od ostatniego razu. W tym miejscu do gry wchodzą selektory.

Selektor jest czystą funkcją, która zachowuje pamięć poprzednich wykonań. Tak długo, jak dane wejściowe nie uległy zmianie, dane wyjściowe nie zostaną ponownie obliczone. Zamiast tego, wyjście zostanie zwrócone z pamięci. Proces ten nazywany jest memoizacją.

NgRx dostarcza funkcję użytkową o nazwie createSelector do budowania selektorów z możliwością memoizacji. Poniżej przedstawiono przykład funkcji użytkowejcreateSelector.

Funkcja createSelector przyjmuje funkcje odwzorowania jeden do wielu, które dają różne wycinki stanu i funkcję projektora, która wykonuje obliczenia. Funkcja projektora nie będzie wywoływana, jeśli plasterki stanu nie zmieniły się od ostatniego wykonania. Aby użyć utworzonej funkcji selektora, należy przekazać ją jako argument do operatora select.

this.isLoggedIn$ = this.store .pipe( select(isLoggedIn) );

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.