Elemente fundamentale ale NgRx: Store, Actions, Reducers, Selectors, Effects

Store-ul este elementul cheie în întregul proces de gestionare a stării. Acesta păstrează starea și facilitează interacțiunea dintre componente și stare. Puteți obține o referință la magazin prin intermediul injecției de dependență Angular, după cum se arată mai jos.

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

Această referință la magazin poate fi utilizată ulterior pentru două operațiuni principale:

  • Pentru a expedia acțiuni către magazin prin intermediul metodei store.dispatch(…), care la rândul lor vor declanșa reductoare și efecte
  • Pentru a prelua starea aplicației prin intermediul unor selectori

Structura unui arbore de obiecte de stare

Să presupunem că aplicația dumneavoastră este formată din două module de caracteristici numite User și Product. Fiecare dintre aceste module gestionează părți diferite ale stării generale. Informațiile despre produs vor fi întotdeauna menținute în secțiunea products din stare. Informațiile despre utilizator vor fi întotdeauna menținute în secțiunea user a stării. Aceste secțiuni sunt, de asemenea, numite „slices”.

Acțiuni

O acțiune este o instrucțiune pe care o expediați către magazin, opțional cu unele metadate (payload). Pe baza tipului de acțiune, magazinul decide ce operații să execute. În cod, o acțiune este reprezentată de un obiect JavaScript simplu și vechi cu două atribute principale, și anume type și payload. payload este un atribut opțional care va fi utilizat de către reducători pentru a modifica starea. Următorul fragment de cod și figura de mai jos ilustrează acest concept.

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

NgRx versiunea 8 oferă o funcție utilitară numită createAction pentru a defini creatorii de acțiuni (nu acțiuni, ci creatori de acțiuni). Urmează un exemplu de cod în acest sens.

Puteți utiliza apoi creatorul de acțiuni login (care este o funcție) pentru a construi acțiuni și a le expedia în magazin, așa cum se arată mai jos. user este obiectul payload pe care îl treceți în acțiune.

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

Reducători

Reducătorii sunt responsabili de modificarea stării și de returnarea unui nou obiect de stare cu modificările. Reducătorii primesc doi parametri, starea curentă și acțiunea. Pe baza tipului de acțiune primit, reducătorii vor efectua anumite modificări ale stării curente și vor produce o nouă stare. Acest concept este prezentat în diagrama de mai jos.

Similar cu acțiunile, NgRx oferă o funcție utilitară numită createReducer pentru a crea reducători. Un apel tipic al funcției createReducer ar semăna cu următorul lucru.

După cum puteți vedea, aceasta preia starea inițială (starea de la pornirea aplicației) și funcții de schimbare a stării de la unul la mai multe care definesc modul de reacție la diferite acțiuni. Fiecare dintre aceste funcții de schimbare a stării primește ca parametri starea curentă și acțiunea și returnează o nouă stare.

Efecte

Efectele vă permit să efectuați efecte secundare atunci când o acțiune este trimisă la magazin. Să încercăm să înțelegem acest lucru printr-un exemplu. Atunci când un utilizator se conectează cu succes la o aplicație, o acțiune cu type Login Action va fi trimisă la magazin cu informațiile despre utilizator în payload. O funcție reducer va asculta această acțiune și va modifica starea cu informațiile despre utilizator. În plus, ca efect secundar, se dorește, de asemenea, salvarea informațiilor despre utilizator în memoria locală a browserului. Un efect poate fi utilizat pentru a efectua această sarcină suplimentară (efect secundar).

Există mai multe moduri de a crea efecte în NgRx. În continuare este prezentată o modalitate brută și autoexplicativă de creare a efectelor. Vă rugăm să rețineți că, în general, nu utilizați această metodă pentru a crea efecte. Am luat-o doar ca exemplu pentru a explica ce se întâmplă în spatele cortinei.

  • actions$ observabilul va emite acțiuni primite de magazin. Aceste valori vor trece printr-un lanț de operatori.
  • ofType este primul operator folosit. Acesta este un operator special furnizat de NgRx (Not RxJS) pentru a filtra acțiunile în funcție de tipul lor. În acest caz, numai acțiunile de tip login vor putea să treacă prin restul lanțului de operatori.
  • tap este al doilea operator utilizat în lanț pentru a stoca informațiile utilizatorului în memoria locală a browserului. Operatorul tap este utilizat în general pentru a efectua efecte secundare într-un lanț de operatori.
  • În cele din urmă, trebuie să ne abonăm manual la observabilul login$.

Cu toate acestea, această abordare are câteva dezavantaje majore.

  • Trebuie să vă abonați manual la observabil, ceea ce nu este o practică bună. În acest fel, va trebui întotdeauna să vă dezabonați manual, ceea ce duce la o lipsă de mentenabilitate.
  • Dacă apare o eroare în lanțul de operatori, observabilul va da eroare și va înceta să mai emită valori (acțiuni) ulterioare. Ca urmare, efectul secundar nu va fi efectuat. Prin urmare, trebuie să aveți un mecanism pentru a crea manual o nouă instanță a observabilului și pentru a relua subscrierea în cazul în care apare o eroare.

Pentru a depăși aceste probleme, NgRx oferă o funcție utilitară numită createEffect pentru a crea efecte. Un apel tipic al funcției createEffect ar arăta în felul următor.

Metoda createEffect primește ca parametri o funcție care returnează un observabil și (opțional) un obiect de configurare.

NgRx se ocupă de abonarea la observabilul returnat de funcția de suport și, prin urmare, nu trebuie să vă abonați sau să vă dezabonați manual. În plus, dacă apare vreo eroare în lanțul operatorului, NgRx va crea un nou observabil și se va resubscrie pentru a se asigura că efectul secundar este întotdeauna executat.

Dacă dispatch este true (valoare implicită) în obiectul de configurare, metoda createEffect returnează un Observable<Action>. În caz contrar, aceasta returnează un Observable<Unknown>. Dacă proprietatea dispatch este true, NgRx se va abona la observabilul returnat de type Observable<Action> și va expedia acțiunile primite în magazin.

Dacă nu mapezi acțiunea primită la un alt tip de acțiune în lanțul de operatori, va trebui să setezi dispatch la false. În caz contrar, execuția va avea ca rezultat o buclă infinită, deoarece aceeași acțiune va fi expediată și primită în fluxul actions$ din nou și din nou. De exemplu, nu trebuie să setați dispatch la false în codul de mai jos, deoarece ați mapat acțiunea originală pe un alt tip de acțiune în lanțul de operatori.

În scenariul de mai sus,

  • Efectul primește acțiuni de type loadAllCourses.
  • Se invocă o API și cursurile sunt încărcate ca efect secundar.
  • Răspunsul API la o acțiune de type allCoursesLoaded este cartografiat și cursurile încărcate sunt transmise ca payload la acțiune.
  • Și, în final, acțiuneaallCoursesLoaded care a fost creată este trimisă la magazin. Acest lucru este realizat de NgRx sub capotă.
  • Un reducer va asculta acțiunea allCoursesLoaded care intră și va modifica starea cu cursurile încărcate.

Selectori

Selectorii sunt funcții pure utilizate pentru a obține porțiuni din starea magazinului. După cum se arată mai jos, puteți interoga starea chiar și fără a utiliza selectori. Dar această abordare vine din nou cu câteva dezavantaje majore.

const isLoggedIn$ = this.store.pipe(map(state => !!state.user));
  • store este un observabil la care vă puteți abona. Ori de câte ori magazinul primește o acțiune, store va împinge obiectul de stare către abonații săi.
  • Puteți utiliza funcții de cartografiere pentru a obține felii de stare și a efectua orice calcul, dacă este necesar. În exemplul de mai sus, obținem felia user din arborele de obiecte de stare și o convertim într-un boolean pentru a determina dacă utilizatorul s-a logat sau nu.
  • Puteți fie să vă abonați manual la observabilul isLoggedIn$, fie să îl utilizați într-un șablon Angular cu pipe async pentru a citi valorile emise.

Cu toate acestea, această abordare are un dezavantaj major. În general, magazinul primește frecvent acțiuni din diferite părți ale aplicației. Conform implementării de mai sus, de fiecare dată când magazinul primește o acțiune, un obiect de stare va fi emis de către magazin. Iar acest obiect de stare va trece din nou prin funcția de cartografiere și va actualiza interfața de utilizator.

Cu toate acestea, dacă rezultatul funcției de cartografiere nu s-a schimbat față de data trecută, nu este nevoie să se actualizeze din nou interfața de utilizator. De exemplu, dacă rezultatul funcției map(state => !!state.user) nu s-a schimbat de la ultima execuție, nu trebuie să transmitem din nou rezultatul către UI/Subscriber. Pentru a realiza acest lucru, NgRx (nu RxJS) a introdus un operator special numit select. Cu operatorul select, codul de mai sus se va schimba după cum urmează.

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

Operatorul select va împiedica împingerea valorilor către UI/abonați dacă rezultatul funcției de cartografiere nu s-a schimbat față de ultima dată.

Această abordare poate fi îmbunătățită în continuare. Chiar dacă operatorul select nu împinge valorile neschimbate către UI/abonați, tot trebuie să ia obiectul de stare și să efectueze calculul pentru a obține rezultatul de fiecare dată.

Așa cum s-a explicat deja mai sus, un state va fi emis de către observabil atunci când magazinul primește o acțiune de la aplicație. O acțiune nu actualizează întotdeauna starea. Dacă starea nu s-a schimbat, nici rezultatul calculului funcției de cartografiere nu se va schimba. Prin urmare, nu trebuie să efectuăm din nou calculul dacă obiectul state emis nu s-a schimbat față de data trecută. Aici intră în joc selectorii.

Un selector este o funcție pură care păstrează o memorie a execuțiilor anterioare. Atâta timp cât intrarea nu s-a schimbat, ieșirea nu va fi recalculată. În schimb, ieșirea va fi returnată din memorie. Acest proces se numește memoizare.

NgRx oferă o funcție utilitară numită createSelector pentru a construi selectori cu capacitate de memoizare. În continuare este prezentat un exemplu al funcției utilitarecreateSelector.

Funcția createSelector primește funcții de cartografiere de la unul la mai multe care oferă diferite felii ale stării și o funcție de proiector care efectuează calculul. Funcția proiector nu va fi invocată dacă feliile de stare nu s-au schimbat de la ultima execuție. Pentru a utiliza funcția selector creată, trebuie să o treceți ca argument la operatorul select.

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

.

Lasă un răspuns

Adresa ta de email nu va fi publicată.