Grundlegende Elemente von NgRx: Store, Actions, Reducers, Selectors, Effects

Der Store ist das Schlüsselelement im gesamten State Management Prozess. Er enthält den Zustand und erleichtert die Interaktion zwischen den Komponenten und dem Zustand. Sie können einen Verweis auf den Store über Angular Dependency Injection erhalten, wie unten gezeigt.

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

Diese Store-Referenz kann anschließend für zwei primäre Operationen verwendet werden:

  • Um Aktionen über die store.dispatch(…)-Methode an den Store zu senden, die wiederum Reduzierer und Effekte auslösen
  • Um den Anwendungszustand über Selektoren abzurufen

Struktur eines Zustandsobjektbaums

Angenommen, Ihre Anwendung besteht aus zwei Funktionsmodulen namens User und Product. Jedes dieser Module verwaltet unterschiedliche Teile des Gesamtzustands. Die Produktinformationen werden immer im Abschnitt products des Zustands gepflegt. Die Benutzerinformationen werden immer im Abschnitt user des Zustands verwaltet. Diese Abschnitte werden auch Slices genannt.

Aktionen

Eine Aktion ist eine Anweisung, die Sie an den Speicher senden, optional mit einigen Metadaten (Nutzlast). Anhand des Aktionstyps entscheidet der Speicher, welche Operationen ausgeführt werden sollen. Im Code wird eine Aktion durch ein einfaches altes JavaScript-Objekt mit zwei Hauptattributen dargestellt, nämlich type und payload. payload ist ein optionales Attribut, das von Reduzierern verwendet wird, um den Status zu ändern. Der folgende Codeschnipsel und die Abbildung veranschaulichen dieses Konzept.

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

NgRx Version 8 bietet eine Utility-Funktion namens createAction zur Definition von Action Creators (nicht Aktionen, sondern Action Creators). Nachfolgend finden Sie ein Beispiel für diesen Code.

Sie können dann den login Action Creator (der eine Funktion ist) verwenden, um Aktionen zu erstellen und sie an den Speicher zu senden, wie unten gezeigt. user ist das payload Objekt, das man der Aktion übergibt.

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

Reduzierer

Reduzierer sind dafür verantwortlich, den Zustand zu verändern und ein neues Zustandsobjekt mit den Änderungen zurückzugeben. Reducers nehmen zwei Parameter entgegen, den aktuellen Zustand und die Aktion. Auf der Grundlage des empfangenen Aktionstyps führen Reduzierer bestimmte Änderungen am aktuellen Zustand durch und erzeugen einen neuen Zustand. Dieses Konzept wird im folgenden Diagramm veranschaulicht.

Ähnlich wie bei den Actions bietet NgRx eine Utility-Funktion namens createReducer zur Erstellung von Reduceers. Ein typischer createReducer-Funktionsaufruf sieht wie folgt aus.

Wie Sie sehen können, nimmt sie den Anfangszustand (den Zustand beim Start der Anwendung) und eine bis mehrere Zustandsänderungsfunktionen auf, die festlegen, wie auf verschiedene Aktionen reagiert werden soll. Jede dieser Zustandsänderungsfunktionen erhält den aktuellen Zustand und die Aktion als Parameter und gibt einen neuen Zustand zurück.

Effekte

Effekte ermöglichen es, Seiteneffekte auszuführen, wenn eine Aktion an den Speicher gesendet wird. Lassen Sie uns versuchen, dies anhand eines Beispiels zu verstehen. Wenn sich ein Benutzer erfolgreich bei einer Anwendung anmeldet, wird eine Aktion mit type Login Action an den Speicher mit den Benutzerinformationen im Feld payload gesendet. Eine Reduzierfunktion hört auf diese Aktion und ändert den Status mit den Benutzerinformationen. Als Nebeneffekt möchte man außerdem die Benutzerinformationen im lokalen Speicher des Browsers speichern. Ein Effekt kann verwendet werden, um diese zusätzliche Aufgabe (Nebeneffekt) auszuführen.

Es gibt mehrere Möglichkeiten, Effekte in NgRx zu erstellen. Nachfolgend eine grobe und selbsterklärende Art, Effekte zu erstellen. Bitte beachten Sie, dass Sie diese Methode im Allgemeinen nicht zum Erstellen von Effekten verwenden. Ich habe dies nur als Beispiel genommen, um zu erklären, was hinter dem Vorhang passiert.

  • actions$ observable gibt Aktionen aus, die vom Store empfangen werden. Diese Werte durchlaufen eine Operatorenkette.
  • ofType ist der erste verwendete Operator. Dies ist ein spezieller Operator, der von NgRx (Not RxJS) bereitgestellt wird, um Aktionen anhand ihres Typs herauszufiltern. In diesem Fall dürfen nur Aktionen vom Typ login den Rest der Operatorenkette durchlaufen.
  • tap ist der zweite Operator in der Kette, der verwendet wird, um die Benutzerinformationen im lokalen Speicher des Browsers zu speichern. Der tap-Operator wird im Allgemeinen verwendet, um Seiteneffekte in einer Operator-Kette auszuführen.
  • Schließlich müssen wir die login$-Observable manuell abonnieren.

Dieser Ansatz hat jedoch ein paar große Nachteile.

  • Sie müssen die Observable manuell abonnieren, was keine gute Praxis ist. Auf diese Weise müssen Sie sich immer wieder manuell abmelden, was zu mangelnder Wartbarkeit führt.
  • Wenn ein Fehler in der Operatorkette auftaucht, wird die Observable einen Fehler machen und keine weiteren Werte (Aktionen) mehr ausgeben. Infolgedessen wird der Seiteneffekt nicht ausgeführt. Daher müssen Sie über einen Mechanismus verfügen, um manuell eine neue Observable-Instanz zu erstellen und sich erneut anzumelden, wenn ein Fehler auftritt.

Um diese Probleme zu überwinden, bietet NgRx eine Hilfsfunktion namens createEffect zur Erstellung von Effekten. Ein typischer createEffect-Funktionsaufruf würde wie folgt aussehen.

Die createEffect-Methode nimmt eine Funktion auf, die ein Observable und (optional) ein Konfigurationsobjekt als Parameter zurückgibt.

NgRx kümmert sich um die Subskription des Observable, das von der Unterstützungsfunktion zurückgegeben wird, und daher müssen Sie es nicht manuell abonnieren oder abbestellen. Außerdem erstellt NgRx im Falle eines Fehlers in der Operatorkette ein neues Observable und abonniert es erneut, um sicherzustellen, dass der Seiteneffekt immer ausgeführt wird.

Wenn dispatch im Konfigurationsobjekt true (Standardwert) ist, gibt die Methode createEffect ein Observable<Action> zurück. Andernfalls gibt sie ein Observable<Unknown> zurück. Wenn die dispatch-Eigenschaft true ist, abonniert NgRx die zurückgegebene Observable von type Observable<Action> und sendet die empfangenen Aktionen an den Speicher.

Wenn Sie die empfangene Aktion nicht einem anderen Aktionstyp in der Operatorkette zuordnen, müssen Sie dispatch auf false setzen. Andernfalls führt die Ausführung zu einer Endlosschleife, da dieselbe Aktion immer wieder im actions$-Stream versendet und empfangen wird. Im folgenden Code müssen Sie beispielsweise dispatch nicht auf false setzen, da Sie die ursprüngliche Aktion einem anderen Aktionstyp in der Operatorkette zuordnen.

Im obigen Szenario empfängt

  • Effect Aktionen von type loadAllCourses.
  • Eine API wird aufgerufen und Kurse werden als Nebeneffekt geladen.
  • Die API-Antwort auf eine Aktion von type allCoursesLoaded wird gemappt und die geladenen Kurse werden als payload an die Aktion übergeben.
  • Und schließlich wird die erstellteallCoursesLoadedAktion an den Speicher gesendet. Dies wird von NgRx unter der Haube erledigt.
  • Ein Reducer hört auf die eingehende allCoursesLoadedAction und modifiziert den Zustand mit den geladenen Kursen.

Selektoren

Selektoren sind reine Funktionen, die zum Abrufen von Slices des Speicherzustands verwendet werden. Wie unten gezeigt, können Sie den Zustand auch ohne Selektoren abfragen. Aber auch dieser Ansatz hat einige wesentliche Nachteile.

const isLoggedIn$ = this.store.pipe(map(state => !!state.user));
  • store ist ein Observable, das Sie abonnieren können. Immer wenn der Store eine Aktion empfängt, gibt store das Zustandsobjekt an seine Abonnenten weiter.
  • Sie können Mapping-Funktionen verwenden, um Slices des Zustands zu erhalten und bei Bedarf Berechnungen auszuführen. Im obigen Beispiel erhalten wir das user-Slice im Baum des Zustandsobjekts und wandeln es in ein Boolesches um, um festzustellen, ob der Benutzer eingeloggt ist oder nicht.
  • Sie können das isLoggedIn$-Observable entweder manuell abonnieren oder es in einem Angular-Template mit asynchroner Pipe verwenden, um die ausgegebenen Werte zu lesen.

Dieser Ansatz hat jedoch einen großen Nachteil. Im Allgemeinen erhält der Store häufig Aktionen von verschiedenen Teilen der Anwendung. Gemäß der obigen Implementierung wird jedes Mal, wenn der Speicher eine Aktion erhält, ein Zustandsobjekt vom Speicher ausgegeben. Und dieses Zustandsobjekt durchläuft erneut die Mapping-Funktion und aktualisiert die Benutzeroberfläche.

Wenn sich das Ergebnis der Mapping-Funktion jedoch seit dem letzten Mal nicht geändert hat, besteht keine Notwendigkeit, die Benutzeroberfläche erneut zu aktualisieren. Wenn sich zum Beispiel das Ergebnis der map(state => !!state.user) seit der letzten Ausführung nicht geändert hat, müssen wir das Ergebnis nicht erneut an die Benutzeroberfläche/den Abonnenten weitergeben. Um dies zu erreichen, hat NgRx (nicht RxJS) einen speziellen Operator namens select eingeführt. Mit dem select-Operator ändert sich der obige Code wie folgt.

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

Der select-Operator verhindert, dass Werte an UI/Abonnenten gepusht werden, wenn sich das Ergebnis der Mapping-Funktion seit dem letzten Mal nicht geändert hat.

Dieser Ansatz kann weiter verbessert werden. Selbst wenn der select-Operator keine unveränderten Werte an UI/Abonnenten weitergibt, muss er immer noch das Zustandsobjekt nehmen und die Berechnung durchführen, um das Ergebnis jedes Mal abzuleiten.

Wie bereits oben erklärt, wird ein state vom Observable emittiert, wenn der Store eine Aktion von der Anwendung erhält. Eine Aktion aktualisiert nicht immer den Zustand. Wenn sich der Zustand nicht geändert hat, ändert sich auch das Ergebnis der Berechnung der Abbildungsfunktion nicht. Daher müssen wir die Berechnung nicht erneut durchführen, wenn sich das ausgegebene state-Objekt seit dem letzten Mal nicht geändert hat. Hier kommen die Selektoren ins Spiel.

Ein Selektor ist eine reine Funktion, die eine Erinnerung an die vorherigen Ausführungen aufrechterhält. Solange sich die Eingabe nicht geändert hat, wird die Ausgabe nicht neu berechnet. Stattdessen wird die Ausgabe aus dem Speicher zurückgegeben. Dieser Prozess wird Memoisierung genannt.

NgRx bietet eine Utility-Funktion namens createSelector, um Selektoren mit Memoisierungsfähigkeit zu erstellen. Nachfolgend ein Beispiel für die Dienstprogrammfunktion createSelector.

Die Funktion createSelector nimmt ein-zu-vielen Mapping-Funktionen auf, die verschiedene Slices des Zustands liefern, und eine Projektorfunktion, die die Berechnung durchführt. Die Projektorfunktion wird nicht aufgerufen, wenn sich die Zustandsabschnitte seit der letzten Ausführung nicht geändert haben. Um die erstellte Selektorfunktion zu verwenden, müssen Sie sie als Argument an den select-Operator übergeben.

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

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.