10 tips voor betere Redux-architectuur

Mandarijneend - Malcolm Carlaw (CC-BY-2.0)

Toen ik React begon te gebruiken, was er geen Redux. Er was alleen de Flux-architectuur, en ongeveer een dozijn concurrerende implementaties ervan.

Nu zijn er twee duidelijke winnaars voor gegevensbeheer in React: Redux en MobX, en deze laatste is niet eens een Flux-implementatie. Redux heeft zoveel gemerkt dat het niet alleen meer voor React wordt gebruikt. U vindt Redux-architectuurimplementaties voor andere frameworks, waaronder Angular 2. Zie bijvoorbeeld ngrx: store.

Kanttekening: MobX is cool, en ik zou het waarschijnlijk verkiezen boven Redux voor eenvoudige UI's, omdat het minder gecompliceerd en minder uitgebreid is. Dat gezegd hebbende, er zijn een aantal belangrijke functies van Redux die MobX je niet geeft, en het is belangrijk om te begrijpen wat die functies zijn voordat je beslist wat goed is voor je project.
Kanttekening: Relay en Falcor zijn andere interessante oplossingen voor statusbeheer, maar in tegenstelling tot Redux en MobX moeten ze worden ondersteund door respectievelijk GraphQL en Falcor Server en alle Relay-status komt overeen met bepaalde server-persisteerde gegevens. AFAIK biedt geen van beide een goed verhaal voor tijdelijk beheer van de client-side-only. U kunt misschien genieten van de voordelen van beide door Relay of Falcor te combineren en te matchen met Redux of MobX, waarbij onderscheid wordt gemaakt tussen de status van alleen de client en de status van de server. Bottom line: er is vandaag geen duidelijke winnaar voor het staatsmanagement bij de klant. Gebruik het juiste gereedschap voor de taak die moet worden uitgevoerd.

Dan Abramov, de maker van Redux, heeft een aantal geweldige cursussen gemaakt over het onderwerp:

  • Aan de slag met Redux
  • Toepassingen bouwen met Idiomatic Redux

Beide zijn geweldige stapsgewijze zelfstudies waarin de basisprincipes van Redux worden uitgelegd, maar je hebt ook een beter begrip nodig om het maximale uit Redux te halen.

Hier volgen enkele tips waarmee u betere Redux-apps kunt bouwen.

1. Begrijp de voordelen van Redux

Er zijn een paar belangrijke doelen voor Redux die u in gedachten moet houden:

  1. Deterministische weergave wordt weergegeven
  2. Deterministische staatsweergave

Bepaling is belangrijk voor de testbaarheid van toepassingen en het diagnosticeren en oplossen van bugs. Als uw applicatieweergaven en -status niet-deterministisch zijn, is het onmogelijk om te weten of de weergaven en status altijd geldig zijn. Je zou zelfs kunnen zeggen dat niet-determinisme op zichzelf een bug is.

Maar sommige dingen zijn inherent niet-deterministisch. Dingen zoals de timing van gebruikersinvoer en netwerk I / O. Dus hoe kunnen we ooit weten of onze code echt werkt? Eenvoudig: isolatie.

Het hoofddoel van Redux is om statusbeheer te isoleren van I / O-bijwerkingen zoals het weergeven van de weergave of het werken met het netwerk. Wanneer bijwerkingen worden geïsoleerd, wordt code veel eenvoudiger. Het is een stuk eenvoudiger om uw bedrijfslogica te begrijpen en te testen als het niet allemaal verward raakt met netwerkverzoeken en DOM-updates.

Wanneer uw weergave renderen is geïsoleerd van netwerk I / O en statusupdates, kunt u een deterministische weergave renderen, wat betekent: gegeven dezelfde status, zal de weergave altijd dezelfde uitvoer weergeven. Het elimineert de mogelijkheid van problemen zoals race-omstandigheden door asynchrone dingen die willekeurig stukjes van je weergave wegvagen, of stukjes van je staat verminken terwijl je weergave bezig is met renderen.

Wanneer een nieuweling overweegt een weergave te maken, denken ze misschien: "Dit bit heeft het gebruikersmodel nodig, dus ik zal een async-verzoek starten om dat op te halen en wanneer die belofte wordt opgelost, werk ik de gebruikerscomponent bij met hun naam." Dat beetje daar vereist de te doen items, dus dat halen we op, en wanneer de belofte wordt opgelost, zullen we ze herhalen en naar het scherm trekken. "

Er zijn een paar grote problemen met deze aanpak:

  1. U beschikt nooit over alle gegevens die u nodig hebt om op elk willekeurig moment het volledige overzicht te krijgen. Je begint pas gegevens op te halen als de component zijn ding begint te doen.
  2. Verschillende ophaaltaken kunnen op verschillende tijdstippen binnenkomen, waardoor de volgorde waarin dingen gebeuren in de weergavevolgorde subtiel wordt gewijzigd. Om de rendervolgorde echt te begrijpen, moet u kennis hebben van iets dat u niet kunt voorspellen: de duur van elk async-verzoek. Popquiz: Wat wordt in het bovenstaande scenario het eerst weergegeven, de gebruikerscomponent of de taken? Antwoord: Het is een race!
  3. Soms zullen gebeurtenisluisteraars de weergavestatus muteren, wat een andere weergave kan activeren, waardoor de reeks verder wordt gecompliceerd.

Het belangrijkste probleem met het opslaan van uw gegevens in de weergavestatus en het geven van toegang aan async-gebeurtenisluisteraars om die weergavestatus te muteren is dit:

"Niet-determinisme = parallelle verwerking + gedeelde status"
~ Martin Odersky (Scala-ontwerper)
Het combineren van gegevens ophalen, gegevensmanipulatie en weergaveproblemen is een recept voor tijdreizende spaghetti.

Ik weet dat dat nogal cool klinkt op een B-film sci-fi soort manier, maar geloof me, tijdreizende spaghetti is de slechtste smaak die er is!

Wat de flux-architectuur doet, is een strikte scheiding en volgorde hanteren, die zich elke keer aan deze regels houdt:

  1. Eerst komen we in een bekende, vaste staat ...
  2. Vervolgens geven we het beeld weer. Niets kan de status opnieuw wijzigen voor deze renderlus.
  3. In dezelfde staat wordt het beeld altijd op dezelfde manier weergegeven.
  4. Luisteraars van gebeurtenissen luisteren naar gebruikersinvoer en handlers voor netwerkaanvragen. Wanneer ze deze ontvangen, worden acties naar de winkel verzonden.
  5. Wanneer een actie wordt verzonden, wordt de status bijgewerkt naar een nieuwe bekende status en wordt de reeks herhaald. Alleen verzonden acties kunnen de status raken.

Dat is Flux in een notendop: een datastroomarchitectuur in één richting voor uw gebruikersinterface:

Flux-architectuur

Met de Flux-architectuur luistert de weergave naar gebruikersinvoer en vertaalt deze in actieobjecten, die naar de winkel worden verzonden. De winkel werkt de applicatiestatus bij en meldt de weergave om opnieuw te renderen. Natuurlijk is het uitzicht zelden de enige bron van input en gebeurtenissen, maar dat is geen probleem. Extra gebeurtenislisteners verzenden actieobjecten, net als de weergave:

Belangrijk is dat statusupdates in Flux transactief zijn. In plaats van eenvoudigweg een updatemethode in de status aan te roepen of een waarde rechtstreeks te manipuleren, worden actieobjecten naar de winkel verzonden. Een actieobject is een transactierecord. Je kunt het zien als een banktransactie - een record van de aan te brengen wijziging. Wanneer u een storting doet bij uw bank, wordt uw saldo van 5 minuten geleden niet weggevaagd. In plaats daarvan wordt een nieuw saldo toegevoegd aan de transactiegeschiedenis. Actieobjecten voegen een transactiegeschiedenis toe aan uw applicatiestatus.

Actieobjecten zien er als volgt uit:

Wat actieobjecten u geven, is de mogelijkheid om een ​​lopend logboek bij te houden van alle staatstransacties. Dat logboek kan worden gebruikt om de staat op een deterministische manier te reproduceren, wat betekent:

Gegeven dezelfde initiële status en dezelfde transacties in dezelfde volgorde, krijgt u altijd dezelfde status als resultaat.

Dit heeft belangrijke implicaties:

  1. Gemakkelijke testbaarheid
  2. Eenvoudig ongedaan maken / opnieuw doen
  3. Tijdreizen debuggen
  4. Duurzaamheid - Zelfs als de staat wordt weggevaagd, als u een record hebt van elke transactie, kunt u deze reproduceren.

Wie wil er geen beheersing hebben over ruimte en tijd? Transactionele status geeft u tijdreizende superkrachten:

Redux dev tools geschiedenis schuifregelaarweergave

2. Sommige apps hebben geen Redux nodig

Als uw UI-workflow eenvoudig is, kan dit allemaal overkill zijn. Als je een tic-tac-toe-spel maakt, heb je dan echt ongedaan maken / opnieuw nodig? De spellen duren zelden langer dan een minuut. Als de gebruiker het verpest, kun je het spel gewoon resetten en opnieuw laten beginnen.

Als:

  • Gebruikersworkflows zijn eenvoudig
  • Gebruikers werken niet samen
  • U hoeft geen server side events (SSE) of websockets te beheren
  • U haalt gegevens op uit één gegevensbron per weergave

Het is mogelijk dat de opeenvolging van gebeurtenissen in de app waarschijnlijk voldoende eenvoudig is dat de voordelen van de transactiestatus de extra moeite niet waard zijn.

Misschien hoeft u uw app niet te Fluxificeren. Er is een veel eenvoudigere oplossing voor dergelijke apps. Bekijk MobX.

Naarmate de complexiteit van uw app echter toeneemt, naarmate de complexiteit van view state management groeit, neemt de waarde van transactionele status mee en MobX biedt geen out-of-the-box transactiestatusbeheer.

Als:

  • Gebruikersworkflows zijn complex
  • Uw app heeft een grote verscheidenheid aan gebruikersworkflows (houd rekening met zowel reguliere gebruikers als beheerders)
  • Gebruikers kunnen samenwerken
  • U gebruikt websockets of SSE
  • U laadt gegevens van meerdere eindpunten om één weergave te maken

Je zou genoeg kunnen profiteren van een transactiestatusmodel om het de moeite waard te maken. Redux past misschien goed bij je.

Wat hebben websockets en SSE hiermee te maken? Naarmate u meer bronnen van asynchrone I / O toevoegt, wordt het moeilijker om te begrijpen wat er in de app aan de hand is met onbepaald statusbeheer. Een deterministische staat en een staat van staatstransacties vereenvoudigen dergelijke apps radicaal.

Naar mijn mening omvatten de meeste grote SaaS-producten ten minste een paar complexe UI-workflows en moeten ze gebruikmaken van transactionele statusbeheer. De meeste kleine hulpprogramma-apps en eenvoudige prototypes zouden dat niet moeten zijn. Gebruik de juiste tool voor de taak.

3. Begrijp de verloopstukken

Redux = Flux + functionele programmering

Flux schrijft eenrichtingsgegevensstroom en transactiestatus voor met actieobjecten, maar zegt niets over het omgaan met actieobjecten. Dat is waar Redux binnenkomt.

De primaire bouwsteen van Redux-statusbeheer is de reductiefunctie. Wat is een reductiefunctie?

Bij functioneel programmeren wordt het algemene hulpprogramma `reduce ()` of `fold ()` gebruikt om een ​​reductiefunctie toe te passen op elke waarde in een zoeklijst om een ​​enkele uitvoerwaarde te verzamelen. Hier is een voorbeeld van een sommeerreductie toegepast op een JavaScript-array met `Array.prototype.reduce ()`:

In plaats van te werken op arrays, past Redux reductiemiddelen toe op een stroom actieobjecten. Onthoud dat een actieobject er als volgt uitziet:

Laten we het bovenstaande verkleiningsreductiemiddel in een Redux-stijl reductiemiddel veranderen:

Nu kunnen we het op enkele testacties toepassen:

4. Verloopstukken moeten pure functies zijn

Om deterministische toestandsreproductie te bereiken, moeten reductoren pure functies zijn. Geen uitzonderingen. Een pure functie:

  1. Bij dezelfde invoer wordt altijd dezelfde uitvoer geretourneerd.
  2. Heeft geen bijwerkingen.

Belangrijk is dat in JavaScript alle niet-primitieve objecten als referentie aan functies worden doorgegeven. Met andere woorden, als u een object doorgeeft en vervolgens direct een eigenschap op dat object muteert, verandert het object ook buiten de functie. Dat is een bijwerking. Je kunt de volledige betekenis van het aanroepen van de functie niet kennen zonder ook de volledige geschiedenis te kennen van het object dat je hebt gepasseerd. Dat is slecht.

Reducers moeten in plaats daarvan een nieuw object retourneren. U kunt dat bijvoorbeeld doen met `Object.assign ({}, state, {thingToChange})`.

Matrixparameters zijn ook verwijzingen. Je kunt niet zomaar `.push ()` nieuwe items in een array in een verloop plaatsen, omdat `.push ()` een muterende bewerking is. Hetzelfde geldt voor `.pop ()`, `.shift ()`, `.unshift ()`, `.reverse ()`, `.splice ()` en elke andere mutatiemethode.

Als u veilig wilt zijn met arrays, moet u de bewerkingen die u in de status uitvoert, beperken tot de veilige accessormethoden. Gebruik `.concat ()` in plaats van `.push ()`.

Bekijk het geval `ADD_CHAT` in dit chatreductiemiddel:

Zoals u kunt zien, wordt een nieuw object gemaakt met `Object.assign ()`, en voegen we de array toe met `.concat ()` in plaats van `.push ()`.

Persoonlijk maak ik me geen zorgen over het per ongeluk muteren van mijn status, dus de laatste tijd experimenteer ik met het gebruik van onveranderlijke data-API's met Redux. Als mijn staat een onveranderlijk object is, hoef ik niet eens naar de code te kijken om te weten dat het object niet per ongeluk wordt gemuteerd. Ik kwam tot deze conclusie na het werken in een team en het ontdekken van bugs door toevallige statusmutaties.

Zuivere functies zijn veel meer dan dit. Als je Redux gaat gebruiken voor productie-apps, heb je echt een goed begrip nodig van wat pure functies zijn en andere dingen waar je rekening mee moet houden (zoals omgaan met tijd, loggen en willekeurige getallen). Zie "Beheers het JavaScript-interview: wat is een pure functie?" Voor meer informatie.

5. Onthoud: reductiemiddelen moeten de enige bron van waarheid zijn

Alle staten in uw app moeten een enkele bron van waarheid hebben, wat betekent dat de staat op één plek is opgeslagen, en overal waar die staat nodig is, moet deze toegang hebben tot de staat op basis van de enige bron van waarheid.

Het is OK om verschillende bronnen van waarheid voor verschillende dingen te hebben. De URL kan bijvoorbeeld de enige bron van waarheid zijn voor het pad van de gebruikersaanvraag en de URL-parameters. Misschien heeft uw app een configuratieservice die de enige bron van waarheid is voor uw API-URL's. Dat is prima. Echter…

Wanneer u een staat opslaat in een Redux-winkel, moet elke toegang tot die staat worden gemaakt via Redux. Als u zich niet aan dit principe houdt, kan dit resulteren in verouderde gegevens of het soort gedeelde statusmutatieproblemen die Flux en Redux hebben uitgevonden om op te lossen.

Met andere woorden, zonder het principe van de enige bron van waarheid, verlies je mogelijk:

  • Deterministische weergave render
  • Deterministische staatsweergave
  • Eenvoudig ongedaan maken / opnieuw doen
  • Tijdreizen debuggen
  • Gemakkelijke testbaarheid

Redux of redux je staat niet. Als je het halverwege doet, kun je alle voordelen van Redux ongedaan maken.

6. Gebruik constanten voor actietypen

Ik zorg er graag voor dat acties gemakkelijk te traceren zijn naar het verloopstuk dat ze gebruikt wanneer je naar de actiegeschiedenis kijkt. Als al uw acties korte, generieke namen hebben zoals `CHANGE_MESSAGE`, wordt het moeilijker om te begrijpen wat er aan de hand is in uw app. Als actietypen echter meer beschrijvende namen hebben zoals `CHAT :: CHANGE_MESSAGE`, is het duidelijk veel duidelijker wat er aan de hand is.

Als u een typefout maakt en een ongedefinieerde actieconstante verzendt, geeft de app een foutmelding om u van de fout te waarschuwen. Als u een typefout maakt met een actietypereeks, mislukt de actie geruisloos.

Als u alle actietypen voor een verloopstuk boven op het bestand verzamelt, kunt u ook helpen:

  • Houd namen consistent
  • Snel inzicht in de verloop-API
  • Bekijk wat er is gewijzigd in pull-aanvragen

7. Gebruik Action Creators om Action Logic van Dispatch Callers te ontkoppelen

Als ik mensen vertel dat ze geen ID's kunnen genereren of de huidige tijd in een verloopstuk kunnen vastpakken, krijg ik een grappig uiterlijk. Als je nu verdacht naar je scherm staart, wees gerust: je bent niet de enige.

Dus waar is een goede plek om met onzuivere logica om te gaan zonder het overal te herhalen waar je de actie nodig hebt? In een actie maker.

Actiemakers hebben nog andere voordelen:

  • Houd actietype constanten ingekapseld in uw verkleinerbestand, zodat u ze nergens anders hoeft te importeren.
  • Voer enkele berekeningen uit op ingangen voordat de actie wordt verzonden.
  • Ketelplaat verkleinen

Laten we een maker van acties gebruiken om het actieobject `ADD_CHAT` te genereren:

Zoals je hierboven kunt zien, gebruiken we cuid om willekeurige id's voor elk chatbericht te genereren en `Date.now ()` om de tijdstempel te genereren. Beide zijn onzuivere bewerkingen die niet veilig in het verloopstuk kunnen worden uitgevoerd, maar het is prima om ze in actie-makers uit te voeren.

Verminder ketelplaat met Action Creators

Sommige mensen denken dat het gebruik van actie-makers boilerplate toevoegt aan het project. Integendeel, je staat op het punt te zien hoe ik ze gebruik om de boilerplate in mijn verloopstukken sterk te verminderen.

Tip: als u uw constanten, verkleiner en actie-makers allemaal in hetzelfde bestand opslaat, vermindert u de benodigde boilerplate wanneer u ze vanuit verschillende locaties importeert.

Stel je voor dat we de mogelijkheid voor een chatgebruiker willen toevoegen om hun gebruikersnaam en beschikbaarheidsstatus aan te passen. We kunnen een paar handlers van het actietype op deze manier toevoegen:

Voor grotere verloopstukken kan dit uitgroeien tot veel boilerplate. Veel reductiemiddelen die ik heb gebouwd, kunnen veel complexer worden dan dat, met veel redundante code. Wat als we alle eenvoudige acties voor eigenschapsverandering samen zouden kunnen samenvouwen?

Blijkt, dat is eenvoudig:

Zelfs met de extra tussenruimte en de extra opmerking, is deze versie korter - en dit zijn slechts twee gevallen. De besparingen kunnen behoorlijk oplopen.

Is niet schakelen ... zaak gevaarlijk? Ik zie een val door!

Je hebt misschien ergens gelezen dat 'schakel'-uitspraken moeten worden vermeden, met name zodat we onbedoeld doorvallen kunnen voorkomen en omdat de lijst met gevallen kan opzwellen. Je hebt misschien gehoord dat je nooit opzettelijk doorval moet gebruiken, omdat het moeilijk is om onbedoelde doorval-bugs te vangen. Dat is allemaal goed advies, maar laten we goed nadenken over de gevaren die ik hierboven heb genoemd:

  • Verloopstukken zijn configureerbaar, dus opgeblazenheid is geen probleem. Als je lijst met cases te groot wordt, breek je de stukken af ​​en verplaats je ze naar afzonderlijke verloopstukken.
  • Elke behuizing keert terug, dus accidentele doorval mag nooit gebeuren. Geen van de gegroepeerde gevallen van doorval mag een ander lichaam hebben dan degene die de vangst uitvoert.

Redux gebruikt goed `switch..case`. Ik verander officieel mijn advies hierover. Zolang je de eenvoudige regels hierboven volgt (schakelaars klein en gericht houden en terugkeren uit elk geval met zijn eigen lichaam), zijn de 'schakel'-verklaringen prima.

Je hebt misschien gemerkt dat deze versie een andere payload vereist. Dit is waar je actie-makers komen:

Zoals u ziet, maken deze actie-makers de vertaling tussen de argumenten en de statusvorm. Maar dat is niet alles wat ze doen ...

8. Gebruik ES6-parameterstandaarden voor handtekeningdocumentatie

Als je Tern.js gebruikt met een editor-plug-in (beschikbaar voor populaire editors zoals Sublime Text en Atom), zal het die standaard ES6-toewijzingen lezen en de vereiste interface van je actie-makers afleiden, dus als je ze belt, u kunt intellisense en autocomplete krijgen. Dit neemt ontwikkelaars van cognitieve belastingen weg, omdat ze het vereiste type lading niet hoeven te onthouden of de broncode hoeven te controleren wanneer ze het vergeten.

Als u geen type-inferentieplug-in zoals Tern, TypeScript of Flow gebruikt, zou u dat moeten zijn.

Opmerking: ik geef er de voorkeur aan te vertrouwen op inferenties van standaardtoewijzingen die zichtbaar zijn in de functiehandtekening in tegenstelling tot type-annotaties, omdat:

  1. U hoeft geen Flow of TypeScript te gebruiken om het te laten werken: in plaats daarvan gebruikt u standaard JavaScript.
  2. Als u TypeScript of Flow gebruikt, zijn annotaties overbodig bij standaardtoewijzingen, omdat zowel TypeScript als Flow het type afleiden uit de standaardtoewijzing.
  3. Ik vind het veel leesbaarder als er minder syntaxisruis is.
  4. Je krijgt standaardinstellingen, wat betekent dat, zelfs als je de CI-build op typefouten niet stopt (je zou verrast zijn, veel projecten niet), je nooit een onbedoelde `ongedefinieerde` parameter op de loer hebt in je code.

9. Gebruik selectors voor berekende status en ontkoppeling

Stel je voor dat je de meest complexe chat-app in de geschiedenis van chat-apps bouwt. Je hebt 500k coderegels geschreven en DAN werpt het productteam een ​​nieuwe functie-eis naar je toe die je zal dwingen om de gegevensstructuur van je staat te veranderen.

Geen reden tot paniek. Je was slim genoeg om de rest van de app te ontkoppelen van de vorm van je staat met selectors. Bullet: ontweken.

Voor bijna elk verloopstuk dat ik schrijf, maak ik een selector die eenvoudig alle variabelen exporteert die ik nodig heb om de weergave te construeren. Laten we eens kijken hoe dat eruit zou kunnen zien voor onze eenvoudige chatreductie:

export const getViewState = state => state;

Ja ik weet het. Dat is zo simpel dat het niet eens de moeite waard is. Je denkt misschien dat ik nu gek ben, maar weet je nog die kogel die we eerder ontweken? Wat als we een berekende status wilden toevoegen, zoals een volledige lijst van alle gebruikers die tijdens deze sessie hebben gepraat? Laten we het 'recentActiveUsers' noemen.

Deze informatie is al opgeslagen in onze huidige staat - maar niet op een manier die gemakkelijk te pakken is. Laten we doorgaan en het pakken in `getViewState ()`:

Als u al uw berekende status in selectors plaatst, kunt u:

  1. Verminder de complexiteit van uw verloopstukken en componenten
  2. Koppel de rest van uw app los van uw staat
  3. Gehoorzaam het principe van de enige bron van waarheid, zelfs binnen je verloopstuk

10. Gebruik TDD: schrijf eerst tests

Veel onderzoeken hebben test-first vergeleken met test-after-methoden en helemaal geen tests. De resultaten zijn duidelijk en dramatisch: de meeste onderzoeken laten een reductie van 40-80% in verzendfouten zien als gevolg van het schrijven van tests voordat u functies implementeert.

TDD kan de dichtheid van uw verzendfouten effectief halveren en er is voldoende bewijs om die bewering te staven.

Terwijl ik de voorbeelden in dit artikel schreef, begon ik ze allemaal met unit-tests.

Om fragiele tests te voorkomen, heb ik de volgende fabrieken gemaakt die ik gebruikte om verwachtingen te produceren:

Merk op dat deze beide standaardwaarden bieden, wat betekent dat ik eigenschappen afzonderlijk kan overschrijven om alleen de gegevens te maken waarin ik ben geïnteresseerd voor een bepaalde test.

Dit is hoe ik ze gebruikte:

Opmerking: ik gebruik tape voor unit-testen vanwege de eenvoud. Ik heb ook 2-3 jaar ervaring met Mocha en Jasmine, en diverse ervaring met veel andere frameworks. U moet deze principes kunnen aanpassen aan het kader dat u kiest.

Let op de stijl die ik heb ontwikkeld om geneste tests te beschrijven. Waarschijnlijk vanwege mijn achtergrond met Jasmine en Mocha, wil ik beginnen met het beschrijven van de component die ik test in een buitenste blok, en vervolgens in binnenste blokken, beschrijf wat ik doorgeven aan de component. Binnenin maak ik eenvoudige gelijkwaardigheidsverklaringen die u kunt doen met de functies `deepEqual ()` of `toEqual ()` van uw testbibliotheek.

Zoals je kunt zien, gebruik ik geïsoleerde teststatus en fabrieksfuncties in plaats van hulpprogramma's zoals `beforeEach ()` en `afterEach ()`, wat ik vermijd omdat ze onervaren ontwikkelaars kunnen aanmoedigen om gedeelde status in de test-suite te gebruiken (dat is slecht) .

Zoals je waarschijnlijk al geraden hebt, heb ik drie verschillende soorten tests voor elk verloopstuk:

  1. Directe reductietests, waar je zojuist een voorbeeld van hebt gezien. Deze testen in wezen dat het reductiemiddel de verwachte standaardstatus produceert.
  2. Action creator-testen, die elke actie-creator testen door het reductiemiddel op de actie toe te passen met behulp van een vooraf bepaalde status als startpunt.
  3. Selector tests, die de selectors testen om ervoor te zorgen dat alle verwachte eigenschappen aanwezig zijn, inclusief berekende eigenschappen met verwachte waarden.

Je hebt al een reductietest gezien. Laten we een paar andere voorbeelden bekijken.

Action Creator-tests

Dit voorbeeld is om een ​​aantal redenen interessant. De actie-maker `addChat ()` is niet puur. Dat betekent dat u geen specifieke verwachting kunt maken voor alle geproduceerde eigenschappen, tenzij u waardeveranderingen doorgeeft. Om dit aan te pakken, gebruikten we een pijp, die ik soms gebruik om te voorkomen dat ik extra variabelen creëer die ik niet echt nodig heb. Ik gebruikte het om de gegenereerde waarden te negeren. We zorgen er nog steeds voor dat ze bestaan, maar het kan ons niet schelen wat de waarden zijn. Merk op dat ik het type niet eens controleer. We vertrouwen op type gevolgtrekkingen en standaardwaarden om daar voor te zorgen.

Een pijp is een functioneel hulpprogramma waarmee u een bepaalde invoerwaarde kunt omzetten via een reeks functies die elk de uitvoer van de vorige functie nemen en deze op de een of andere manier transformeren. Ik gebruik lodash pipe van `lodash / fp / pipe`, wat een alias is voor` lodash / flow`. Interessant is dat `pipe ()` zelf kan worden gemaakt met een reductiefunctie:

Ik gebruik meestal 'pipe ()' in de reductiebestanden om de statusovergangen te vereenvoudigen. Alle statusovergangen zijn uiteindelijk gegevensstromen die van de ene gegevensrepresentatie naar de volgende gaan. Dat is waar `pipe () 'goed in is.

Merk op dat de maker van de actie ons ook in staat stelt om alle standaardwaarden te overschrijven, zodat we specifieke id's en tijdstempels kunnen passeren en kunnen testen op specifieke waarden.

Keuzetests

Ten slotte testen we de statuskiezers en zorgen we ervoor dat de berekende waarden correct zijn en dat alles is zoals het zou moeten zijn:

Merk op dat we in deze test `Array.prototype.reduce ()` hebben gebruikt om enkele `addChat ()` -acties te verminderen. Een van de leuke dingen van Redux-verloopstukken is dat het gewoon reguliere verloopfuncties zijn, wat betekent dat je er alles mee kunt doen dat je met een andere verloopfunctie zou doen.

Onze `verwachte 'waarde controleert of al onze chatobjecten in het logboek staan ​​en of de recent actieve gebruikers correct worden vermeld.

Daar valt niet veel over te zeggen.

Redux-regels

Als u Redux correct gebruikt, krijgt u grote voordelen:

  • Elimineer bugs in timingafhankelijkheid
  • Schakel deterministische weergave in
  • Schakel deterministische toestandsreproductie in
  • Eenvoudige functies voor ongedaan maken / opnieuw inschakelen inschakelen
  • Vereenvoudig debuggen
  • Word een tijdreiziger

Maar om dit allemaal te laten werken, moet u enkele regels onthouden:

  • Reductoren moeten pure functies zijn
  • Reducers moeten de enige bron van waarheid zijn voor hun staat
  • De verkleiningsstatus moet altijd serieel zijn
  • De reductiestatus mag geen functies bevatten

Houd ook rekening met:

  • Sommige apps hebben geen Redux nodig
  • Gebruik constanten voor actietypes
  • Gebruik actie-makers om actielogica te ontkoppelen van dispatch-bellers
  • Gebruik de standaardwaarden van de ES6-parameter voor zelfbeschrijvende handtekeningen
  • Gebruik selectors voor de berekende status en ontkoppeling
  • Gebruik altijd TDD!

Genieten!

Ben je klaar om je Redux-vaardigheden te verbeteren met DevAnywhere?

Leer geavanceerde functionele programmering, React en Redux met 1: 1 mentorschap. Lifetime Access-leden, bekijk de functionele programmering en Redux-lessen. Bekijk de Shotgun-serie en rijd met mij mee terwijl ik echte apps bouw met React en Redux.

https://devanywhere.io/

Eric Elliott is de auteur van 'Programming JavaScript Applications' (O'Reilly) en mede-oprichter van DevAnywhere.io. Hij heeft bijgedragen aan software-ervaringen voor Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC en topartiesten zoals Usher, Frank Ocean, Metallica en nog veel meer.

Hij werkt overal waar hij wil met de mooiste vrouw ter wereld.