Fundamentele elementen van NgRx: Store, Actions, Reducers, Selectors, Effects

De store is het belangrijkste element in het hele state management proces. Het houdt de toestand vast en vergemakkelijkt de interactie tussen de componenten en de toestand. Je kunt een verwijzing naar de store krijgen via Angular dependency injection, zoals hieronder te zien is.

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

Deze store-referentie kan vervolgens worden gebruikt voor twee primaire bewerkingen:

  • Om acties te dispatchen naar de store via de store.dispatch(…) methode, die op hun beurt reducers en effecten zullen triggeren
  • Om de applicatietoestand op te halen via selectors

Structuur van een state object tree

Voorstel dat uw applicatie bestaat uit twee feature modules genaamd User en Product. Elk van deze modules behandelt verschillende delen van de algemene toestand. Product informatie zal altijd worden onderhouden in de products sectie in de toestand. Gebruikersinformatie zal altijd worden onderhouden in de user sectie van de status. Deze secties worden ook wel slices genoemd.

Actions

Een action is een instructie die u naar de store verzendt, eventueel met wat metadata (payload). Op basis van het actietype beslist de winkel welke bewerkingen moeten worden uitgevoerd. In code wordt een actie voorgesteld door een gewoon JavaScript-object met twee hoofdattributen, namelijk type en payload. payload is een optioneel attribuut dat door reducers zal worden gebruikt om de status te wijzigen. De volgende code snippet en figuur illustreren dit concept.

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

NgRx versie 8 biedt een utility functie genaamd createAction om action creators te definiëren (geen acties, maar action creators). Hieronder volgt een voorbeeldcode hiervoor.

U kunt dan de login action creator (die een functie is) gebruiken om acties te bouwen en ze naar de winkel te sturen, zoals hieronder wordt weergegeven. user is het payload object dat u doorgeeft aan de action.

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

Reducers

Reducers zijn verantwoordelijk voor het wijzigen van de state en het retourneren van een nieuw state object met de wijzigingen. Reducers nemen twee parameters in, de huidige status en de actie. Gebaseerd op het ontvangen type actie, zullen reducers bepaalde modificaties uitvoeren op de huidige status en een nieuwe status produceren. Dit concept wordt getoond in het diagram hieronder.

Gelijkaardig aan acties, voorziet NgRx een nutsfunctie genaamd createReducer om reducers te creëren. Een typische createReducer-functieaanroep ziet er als volgt uit.

Zoals u kunt zien, neemt deze functie de begintoestand (de toestand bij het opstarten van de toepassing) en een-op-een-statusveranderingsfuncties op die definiëren hoe op verschillende acties moet worden gereageerd. Elk van deze toestandsveranderingsfuncties ontvangt de huidige toestand en de actie als parameters, en retourneert een nieuwe toestand.

Effecten

Effecten stellen u in staat om neveneffecten uit te voeren wanneer een actie naar de winkel wordt verzonden. Laten we proberen om dit te begrijpen door middel van een voorbeeld. Wanneer een gebruiker succesvol inlogt in een applicatie, zal een actie met type Login Action worden verzonden naar de winkel met de gebruikersinformatie in de payload. Een reducer functie luistert naar deze actie en wijzigt de status met de gebruikersinformatie. Daarnaast wil je als neveneffect ook de gebruikersinformatie opslaan in de lokale opslag van de browser. Een effect kan worden gebruikt om deze extra taak (side effect) uit te voeren.

Er zijn meerdere manieren om effecten te maken in NgRx. Hieronder volgt een ruwe en voor zichzelf sprekende manier om effecten te maken. Houd er rekening mee dat je deze methode over het algemeen niet gebruikt om effecten te maken. Ik heb dit alleen als voorbeeld genomen om uit te leggen wat er achter het gordijn gebeurt.

  • actions$ observable zal acties uitzenden die door de winkel worden ontvangen. Deze waarden zullen door een operator keten gaan.
  • ofType is de eerste operator die gebruikt wordt. Dit is een speciale operator van NgRx (Not RxJS) om acties te filteren op basis van hun type. In dit geval zullen alleen acties van het type login worden toegestaan om door de rest van de operator keten te gaan.
  • tap is de tweede operator die in de keten wordt gebruikt om de gebruikersinformatie op te slaan in de lokale opslag van de browser. De tap operator wordt over het algemeen gebruikt om neveneffecten in een operator chain uit te voeren.
  • Ten slotte moeten we ons handmatig abonneren op de login$ observable.

Deze aanpak heeft echter een paar grote nadelen.

  • Je moet je handmatig abonneren op de observable, wat geen goede praktijk is. Op deze manier moet je je altijd handmatig uitschrijven, wat leidt tot een gebrek aan onderhoudbaarheid.
  • Als er een fout optreedt in de operator keten, zal de observeerbare een error out krijgen en stoppen met het uitzenden van opeenvolgende waarden (acties). Als gevolg daarvan zal het neveneffect niet worden uitgevoerd. Daarom moet je een mechanisme hebben om handmatig een nieuwe instantie van de observeerbare te maken en opnieuw uit te schrijven als er een fout optreedt.

Om deze problemen op te lossen, biedt NgRx een utility-functie genaamd createEffect om effecten te creëren. Een typische createEffect-functieaanroep ziet er als volgt uit.

De createEffect-methode neemt een functie in die een observeerbare en (optioneel) een configuratieobject als parameters retourneert.

NgRx behandelt de inschrijving op de observeerbare die door de ondersteuningsfunctie wordt geretourneerd, en daarom hoeft u zich niet handmatig aan of af te melden. Bovendien, als er een fout optreedt in de operator keten, zal NgRx een nieuwe observeerbare maken en zich opnieuw abonneren om ervoor te zorgen dat het neveneffect altijd wordt uitgevoerd.

Als dispatch true is (standaard waarde) in het configuratie object, retourneert de createEffect methode een Observable<Action>. Anders retourneert het een Observable<Unknown>. Als dispatch eigenschap true is, zal NgRx zich abonneren op de geretourneerde observeerbare van type Observable<Action> en de ontvangen acties naar de winkel verzenden.

Als u de ontvangen actie niet aan een ander type actie in de operatorketen toewijst, moet u dispatch op false instellen. Anders zal de uitvoering resulteren in een oneindige lus, omdat dezelfde actie zal worden verzonden en ontvangen in de actions$ stroom opnieuw en opnieuw. U hoeft bijvoorbeeld dispatch niet op false te zetten in de onderstaande code, omdat u de oorspronkelijke actie toewijst aan een ander type actie in de operatorketen.

In het bovenstaande scenario ontvangt

  • Effect acties van type loadAllCourses.
  • Effect roept een API op en als neveneffect worden cursussen geladen.
  • De API-respons op een actie van type allCoursesLoaded wordt in kaart gebracht en de geladen cursussen worden als payload aan de actie doorgegeven.
  • En ten slotte wordt deallCoursesLoaded-actie die is aangemaakt, naar de winkel verzonden. Dit wordt gedaan door NgRx onder de motorkap.
  • Een reducer zal luisteren naar de inkomende allCoursesLoaded actie en de staat wijzigen met de geladen cursussen.

Selectors

Selectors zijn pure functies die worden gebruikt voor het verkrijgen van slices van de winkel staat. Zoals hieronder getoond, kun je de status ook opvragen zonder selectors te gebruiken. Maar deze aanpak heeft weer een paar grote nadelen.

const isLoggedIn$ = this.store.pipe(map(state => !!state.user));
  • store is een observeerbare waarop u zich kunt abonneren. Telkens wanneer de winkel een actie ontvangt, zal store het state object naar zijn abonnees pushen.
  • Je kunt mapping functies gebruiken om slices van state te verkrijgen en indien nodig berekeningen uit te voeren. In het bovenstaande voorbeeld verkrijgen we de user slice in de state object tree en zetten deze om in een boolean om te bepalen of de gebruiker is ingelogd of niet.
  • Je kunt je handmatig abonneren op de isLoggedIn$ observable of deze gebruiken in een Angular template met async pipe om de uitgestuurde waarden te lezen.

Deze aanpak heeft echter een groot nadeel. In het algemeen, de winkel ontvangt acties vaak uit verschillende delen van de applicatie. Volgens de bovenstaande implementatie, elke keer dat de winkel een actie ontvangt, zal een state object worden geëmitteerd door de winkel. En dit state object zal opnieuw door de mapping functie gaan en de UI updaten.

Hoewel, als het resultaat van de mapping functie niet is veranderd ten opzichte van de vorige keer, is het niet nodig om de UI opnieuw te updaten. Bijvoorbeeld, als het resultaat van de map(state => !!state.user) niet is veranderd ten opzichte van de laatste uitvoering, hoeven we het resultaat niet opnieuw naar de UI/Subscriber te pushen. Om dit te bereiken, heeft NgRx (niet RxJS) een speciale operator geïntroduceerd, select genaamd. Met de select operator zal de bovenstaande code als volgt veranderen.

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

De select operator zal voorkomen dat waarden worden gepushed naar UI/abonnees als het resultaat van de mapping functie niet is veranderd ten opzichte van de vorige keer.

Deze aanpak kan verder worden verbeterd. Zelfs als de select operator geen ongewijzigde waarden naar UI/Subscribers pusht, moet het nog steeds het state object nemen en de berekening uitvoeren om het resultaat elke keer af te leiden.

Zoals hierboven al uitgelegd, zal een state worden uitgezonden door de observeerbare wanneer de winkel een actie ontvangt van de applicatie. Een actie update niet altijd de status. Als de toestand niet veranderd is, zal het resultaat van de mapping functie berekening ook niet veranderen. Daarom hoeven we de berekening niet opnieuw uit te voeren als het state object niet is veranderd sinds de laatste keer. Dit is waar de selectors in het spel komen.

Een selector is een zuivere functie die een geheugen van de vorige uitvoeringen bijhoudt. Zolang de invoer niet is veranderd, wordt de uitvoer niet opnieuw berekend. In plaats daarvan wordt de uitvoer uit het geheugen geretourneerd. Dit proces wordt memoization genoemd.

NgRx biedt een utility functie genaamd createSelector om selectors met memoization mogelijkheid te bouwen. Hieronder volgt een voorbeeld van decreateSelector utiliteitsfunctie.

De createSelector functie neemt één-op-veel mapping functies in die verschillende segmenten van de toestand geven en een projector functie die de berekening uitvoert. De projectorfunctie wordt niet aangeroepen als de toestandsegmenten niet zijn veranderd sinds de laatste uitvoering. Om de aangemaakte selector functie te gebruiken, moet je deze als argument doorgeven aan de select operator.

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

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.