10 consigli per una migliore architettura Redux

Mandarin Duck - Malcolm Carlaw (CC-BY-2.0)

Quando ho iniziato a usare React, non c'era Redux. C'era solo l'architettura Flux e circa una dozzina di implementazioni concorrenti di essa.

Ora ci sono due chiari vincitori per la gestione dei dati in React: Redux e MobX, e quest'ultimo non è nemmeno un'implementazione Flux. Redux ha attaccato così tanto che non viene più utilizzato solo per React. Puoi trovare implementazioni dell'architettura Redux per altri framework, incluso Angular 2. Vedi ad esempio ngrx: store.

Nota a margine: MobX è interessante e probabilmente lo sceglierei su Redux per semplici interfacce utente, perché è meno complicato e meno dettagliato. Detto questo, ci sono alcune importanti funzionalità di Redux che MobX non ti offre, ed è importante capire quali sono queste caratteristiche prima di decidere quale è giusto per il tuo progetto.
Nota a margine: Relay e Falcor sono altre soluzioni interessanti per la gestione dello stato, ma a differenza di Redux e MobX, devono essere supportati da GraphQL e Falcor Server, rispettivamente, e tutto lo stato del relè corrisponde ad alcuni dati persistenti sul server. AFAIK, nessuno dei due offre una buona storia per la gestione transitoria dello stato solo lato client. Potresti essere in grado di godere dei vantaggi di entrambi mescolando e abbinando Relay o Falcor con Redux o MobX, differenziando tra stato solo client e stato persistente del server. In conclusione: oggi non esiste un chiaro vincitore singolo per la gestione dello stato sul client. Utilizzare lo strumento giusto per il lavoro da svolgere.

Dan Abramov, il creatore di Redux ha fatto un paio di grandi corsi sull'argomento:

  • Introduzione a Redux
  • Creazione di applicazioni con Idiomatic Redux

Entrambi sono ottimi tutorial passo-passo che spiegano le basi di Redux, ma avrai anche bisogno di una comprensione di livello superiore per ottenere il massimo da Redux.

Di seguito sono riportati alcuni suggerimenti che ti aiuteranno a creare app Redux migliori.

1. Comprendere i vantaggi di Redux

Ci sono un paio di obiettivi importanti per Redux che devi tenere a mente:

  1. Rendering deterministici della vista
  2. Riproduzione deterministica dello stato

Il determinismo è importante per la testabilità dell'applicazione e per la diagnosi e la correzione di bug. Se le visualizzazioni e lo stato dell'applicazione non sono deterministici, è impossibile sapere se le visualizzazioni e lo stato saranno sempre validi. Si potrebbe anche dire che il non determinismo è di per sé un bug.

Ma alcune cose sono intrinsecamente non deterministiche. Cose come i tempi di input dell'utente e I / O di rete. Quindi, come possiamo sapere se il nostro codice funziona davvero? Facile: isolamento.

Lo scopo principale di Redux è isolare la gestione dello stato dagli effetti collaterali I / O come il rendering della vista o l'utilizzo della rete. Quando gli effetti collaterali sono isolati, il codice diventa molto più semplice. È molto più semplice comprendere e testare la tua logica aziendale quando non è tutto intricato con richieste di rete e aggiornamenti DOM.

Quando il rendering della vista è isolato dall'I / O di rete e dagli aggiornamenti di stato, è possibile ottenere un rendering deterministico della vista, ovvero: dato lo stesso stato, la vista renderà sempre lo stesso output. Elimina la possibilità di problemi come le condizioni di gara da cose asincrone che cancellano casualmente i bit della tua vista o mutilano i bit del tuo stato mentre la tua vista è in procinto di renderizzare.

Quando un principiante pensa alla creazione di una vista, potrebbe pensare: "Questo bit ha bisogno del modello utente, quindi avvierò una richiesta asincrona per recuperarlo e quando quella promessa si risolve, aggiornerò il componente utente con il loro nome. Quel po 'laggiù richiede le cose da fare, quindi lo recupereremo e, quando la promessa si risolverà, le passeremo in rassegna e le disegneremo sullo schermo ".

Ci sono alcuni problemi principali con questo approccio:

  1. Non hai mai tutti i dati necessari per rendere la vista completa in un dato momento. In realtà non inizi a recuperare i dati finché il componente non inizia a fare le sue cose.
  2. Diverse attività di recupero possono entrare in momenti diversi, cambiando sottilmente l'ordine in cui le cose accadono nella sequenza di rendering della vista. Per comprendere veramente la sequenza di rendering, devi conoscere qualcosa che non puoi prevedere: la durata di ogni richiesta asincrona. Pop quiz: nello scenario sopra, cosa viene visualizzato per primo, il componente utente o le cose da fare? Risposta: è una gara!
  3. A volte i listener di eventi mutano lo stato di visualizzazione, il che potrebbe innescare un altro rendering, complicando ulteriormente la sequenza.

Il problema chiave con l'archiviazione dei dati nello stato di visualizzazione e la possibilità di accedere ai listener di eventi asincroni per modificare tale stato di visualizzazione è questo:

"Non determinismo = elaborazione parallela + stato condiviso"
~ Martin Odersky (designer Scala)
Mescolare il recupero dei dati, la manipolazione dei dati e la visualizzazione dei problemi di rendering è una ricetta per gli spaghetti che viaggiano nel tempo.

So che suona un po 'figo in un modo fantascientifico di film B, ma credetemi, gli spaghetti nel tempo sono il peggior tipo di degustazione che ci sia!

Ciò che l'architettura del flusso fa è imporre una rigorosa separazione e sequenza, che obbedisce a queste regole ogni volta:

  1. Innanzitutto, entriamo in uno stato noto e fisso ...
  2. Quindi eseguiamo il rendering della vista. Nulla può cambiare di nuovo lo stato per questo ciclo di rendering.
  3. Dato lo stesso stato, la vista verrà sempre visualizzata allo stesso modo.
  4. I listener di eventi ascoltano l'input dell'utente e i gestori delle richieste di rete. Quando li ottengono, le azioni vengono inviate al negozio.
  5. Quando viene inviata un'azione, lo stato viene aggiornato con un nuovo stato noto e la sequenza si ripete. Solo le azioni inviate possono toccare lo stato.

Questo è Flux in breve: un'architettura di flusso di dati unidirezionale per l'interfaccia utente:

Flux Architecture

Con l'architettura Flux, la vista ascolta l'input dell'utente, li traduce in oggetti azione, che vengono spediti al negozio. Il negozio aggiorna lo stato dell'applicazione e notifica la visualizzazione per eseguire nuovamente il rendering. Naturalmente, la vista è raramente l'unica fonte di input ed eventi, ma non è un problema. I listener di eventi aggiuntivi inviano oggetti azione, proprio come la vista:

È importante sottolineare che gli aggiornamenti di stato in Flux sono transazionali. Invece di chiamare semplicemente un metodo di aggiornamento sullo stato o manipolare direttamente un valore, gli oggetti azione vengono spediti al negozio. Un oggetto azione è un record di transazione. Puoi pensarlo come una transazione bancaria: una registrazione della modifica da apportare. Quando effettui un deposito presso la tua banca, il saldo di 5 minuti fa non viene cancellato. Invece, un nuovo saldo viene aggiunto alla cronologia delle transazioni. Gli oggetti azione aggiungono una cronologia delle transazioni allo stato dell'applicazione.

Gli oggetti azione hanno questo aspetto:

Ciò che gli oggetti azione ti danno è la possibilità di mantenere un registro in esecuzione di tutte le transazioni di stato. Quel registro può essere utilizzato per riprodurre lo stato in modo deterministico, ovvero:

Dato lo stesso stato iniziale e le stesse transazioni nello stesso ordine, si ottiene sempre lo stesso stato di conseguenza.

Ciò ha importanti implicazioni:

  1. Testabilità facile
  2. Annulla / ripristina facilmente
  3. Debug dei viaggi nel tempo
  4. Durata - Anche se lo stato viene cancellato, se si dispone di un record di ogni transazione, è possibile riprodurlo.

Chi non vuole avere una padronanza dello spazio e del tempo? Lo stato transazionale ti dà superpoteri che viaggiano nel tempo:

Visualizzazione del cursore della cronologia degli strumenti di sviluppo Redux

2. Alcune app non necessitano di Redux

Se il flusso di lavoro dell'interfaccia utente è semplice, tutto ciò potrebbe essere eccessivo. Se stai realizzando un gioco tic-tac-toe, hai davvero bisogno di annullare / ripetere? I giochi durano raramente più di un minuto. Se l'utente sbaglia, potresti semplicemente ripristinare il gioco e lasciarlo ricominciare.

Se:

  • I flussi di lavoro dell'utente sono semplici
  • Gli utenti non collaborano
  • Non è necessario gestire eventi lato server (SSE) o WebSocket
  • È possibile recuperare i dati da una singola origine dati per vista

È possibile che la sequenza di eventi nell'app sia probabilmente sufficientemente semplice che i vantaggi dello stato transazionale non valgono lo sforzo extra.

Forse non è necessario Fluxify la tua app. Esiste una soluzione molto più semplice per app come quella. Dai un'occhiata a MobX.

Tuttavia, con l'aumentare della complessità della tua app, con l'aumentare della complessità della gestione dello stato di visualizzazione, il valore dello stato transazionale aumenta con esso e MobX non fornisce una gestione dello stato transazionale pronta all'uso.

Se:

  • I flussi di lavoro dell'utente sono complessi
  • La tua app ha una grande varietà di flussi di lavoro degli utenti (considera sia gli utenti normali che gli amministratori)
  • Gli utenti possono collaborare
  • Stai utilizzando socket Web o SSE
  • Stai caricando dati da più endpoint per creare un'unica vista

Potresti beneficiare abbastanza da un modello di stato transazionale per far valere la pena. Redux potrebbe essere adatto a te.

Che cosa hanno a che fare Web Socket e SSE? Man mano che aggiungi più fonti di I / O asincrono, diventa più difficile capire cosa sta succedendo nell'app con una gestione dello stato indeterminata. Lo stato deterministico e un registro delle transazioni statali semplificano radicalmente app come questa.

A mio avviso, la maggior parte dei prodotti SaaS di grandi dimensioni coinvolge almeno alcuni complessi flussi di lavoro dell'interfaccia utente e dovrebbe utilizzare la gestione dello stato transazionale. La maggior parte delle piccole app di utilità e dei semplici prototipi non dovrebbero. Usa lo strumento giusto per il lavoro.

3. Comprendere i riduttori

Redux = Flux + Programmazione funzionale

Flux prescrive flusso di dati unidirezionale e stato transazionale con oggetti azione, ma non dice nulla su come gestire gli oggetti azione. Ecco dove entra in gioco Redux.

Il componente principale della gestione dello stato di Redux è la funzione di riduzione. Che cos'è una funzione di riduzione?

Nella programmazione funzionale, l'utilità comune `reduce ()` o `fold ()` viene utilizzata per applicare una funzione di riduzione a ciascun valore in un elenco di valori al fine di accumulare un singolo valore di output. Ecco un esempio di un riduttore di somma applicato a un array JavaScript con `Array.prototype.reduce ()`:

Invece di operare su array, Redux applica riduttori a un flusso di oggetti azione. Ricorda, un oggetto azione è simile al seguente:

Trasformiamo il riduttore di somma sopra in un riduttore in stile Redux:

Ora possiamo applicarlo ad alcune azioni di test:

4. I riduttori devono essere funzioni pure

Per ottenere la riproduzione dello stato deterministico, i riduttori devono essere funzioni pure. Nessuna eccezione. Una pura funzione:

  1. Dato lo stesso input, restituisce sempre lo stesso output.
  2. Non ha effetti collaterali.

È importante sottolineare che in JavaScript, tutti gli oggetti non primitivi vengono passati a funzioni come riferimenti. In altre parole, se passi un oggetto e poi muti direttamente una proprietà su quell'oggetto, anche l'oggetto cambia al di fuori della funzione. Questo è un effetto collaterale. Non puoi conoscere il significato completo di chiamare la funzione senza conoscere anche la cronologia completa dell'oggetto che hai passato. È un male.

I riduttori dovrebbero invece restituire un nuovo oggetto. Puoi farlo con `Object.assign ({}, state, {thingToChange})`, per esempio.

Anche i parametri dell'array sono riferimenti. Non puoi semplicemente `.push ()` nuovi elementi in un array in un riduttore, perché `.push ()` è un'operazione di mutazione. Allo stesso modo, lo sono anche `.pop ()`, `.shift ()`, `.unshift ()`, `.reverse ()`, `.splice ()` e qualsiasi altro metodo mutatore.

Se si desidera essere sicuri con le matrici, è necessario limitare le operazioni eseguite sullo stato ai metodi di accesso sicuro. Invece di `.push ()`, usa `.concat ()`.

Dai un'occhiata al caso `ADD_CHAT` in questo riduttore di chat:

Come puoi vedere, un nuovo oggetto viene creato con `Object.assign ()`, e aggiungiamo all'array con `.concat ()` invece di `.push ()`.

Personalmente, non mi preoccupo di mutare accidentalmente il mio stato, quindi ultimamente ho sperimentato l'uso di API di dati immutabili con Redux. Se il mio stato è un oggetto immutabile, non ho nemmeno bisogno di guardare il codice per sapere che l'oggetto non viene mutato accidentalmente. Sono giunto a questa conclusione dopo aver lavorato in una squadra e scoperto bug da mutazioni accidentali di stato.

C'è molto di più nelle funzioni pure di questo. Se hai intenzione di utilizzare Redux per le app di produzione, hai davvero bisogno di una buona comprensione di quali siano le funzioni pure e di altre cose di cui devi essere consapevole (come gestire il tempo, la registrazione e i numeri casuali). Per ulteriori informazioni, vedi "Padroneggia l'intervista a JavaScript: cos'è una funzione pura?".

5. Ricorda: i riduttori devono essere l'unica fonte di verità

Tutto lo stato nella tua app dovrebbe avere un'unica fonte di verità, il che significa che lo stato è memorizzato in un unico posto e in qualsiasi altro luogo tale stato dovrebbe accedere allo stato facendo riferimento alla sua singola fonte di verità.

Va bene avere diverse fonti di verità per cose diverse. Ad esempio, l'URL potrebbe essere l'unica fonte di verità per il percorso della richiesta dell'utente e i parametri URL. Forse la tua app ha un servizio di configurazione che è l'unica fonte di verità per i tuoi URL API. Va bene. Però…

Quando memorizzi uno stato in un negozio Redux, qualsiasi accesso a tale stato dovrebbe essere effettuato tramite Redux. La mancata osservanza di questo principio può comportare dati non aggiornati o il tipo di bug di mutazione dello stato condiviso che Flux e Redux sono stati inventati per risolvere.

In altre parole, senza il principio della singola fonte di verità, potresti perdere:

  • Rendering vista deterministica
  • Riproduzione statale deterministica
  • Annulla / ripristina facilmente
  • Debug dei viaggi nel tempo
  • Testabilità facile

O Redux o non Redux il tuo stato. Se lo fai a metà strada, potresti annullare tutti i vantaggi di Redux.

6. Utilizzare le costanti per i tipi di azione

Mi piace assicurarmi che le azioni siano facilmente rintracciabili nel riduttore che le impiega quando si guarda la cronologia delle azioni. Se tutte le tue azioni hanno nomi generici brevi come `CHANGE_MESSAGE`, diventa più difficile capire cosa sta succedendo nella tua app. Tuttavia, se i tipi di azione hanno nomi più descrittivi come `CHAT :: CHANGE_MESSAGE`, è ovviamente molto più chiaro cosa sta succedendo.

Inoltre, se esegui un refuso e invii una costante di azione indefinita, l'app genererà un errore per avvisarti dell'errore. Se si effettua un errore di battitura con una stringa del tipo di azione, l'azione fallirà silenziosamente.

Mantenere tutti i tipi di azioni per un riduttore raccolti in un punto nella parte superiore del file può anche aiutarti:

  • Mantieni i nomi coerenti
  • Comprendi rapidamente l'API del riduttore
  • Scopri cosa è cambiato nelle richieste pull

7. Utilizzare i creatori di azioni per separare la logica di azione dai chiamanti di invio

Quando dico alle persone che non possono generare ID o prendere l'ora corrente in un riduttore, ottengo un aspetto divertente. Se stai fissando lo schermo con sospetto in questo momento, stai certo: non sei solo.

Allora, dove è un buon posto per gestire una logica impura come quella senza ripeterla ovunque sia necessario usare l'azione? In un creatore di azioni.

I creatori di azioni hanno anche altri vantaggi:

  • Mantieni le costanti del tipo di azione incapsulate nel file del riduttore in modo da non doverle importare altrove.
  • Effettuare alcuni calcoli sugli input prima di inviare l'azione.
  • Ridurre la piastra della caldaia

Usiamo un creatore di azioni per generare l'oggetto azione `ADD_CHAT`:

Come puoi vedere sopra, stiamo usando cuid per generare ID casuali per ogni messaggio di chat e `Date.now ()` per generare il timestamp. Entrambe sono operazioni impure che non sono sicure da eseguire nel riduttore, ma è perfettamente OK eseguirle in creatori di azioni.

Ridurre la caldaia con Action Creators

Alcune persone pensano che l'uso di creatori di azioni aggiunga un modello al progetto. Al contrario, stai per vedere come li uso per ridurre notevolmente la piastra della caldaia nei miei riduttori.

Suggerimento: se memorizzi le costanti, il riduttore e i creatori di azioni tutti nello stesso file, ridurrai la piastra di caldaia necessaria quando li importi da posizioni separate.

Immagina di voler aggiungere la possibilità per un utente della chat di personalizzare il proprio nome utente e lo stato di disponibilità. Potremmo aggiungere un paio di gestori del tipo di azione al riduttore in questo modo:

Per i riduttori più grandi, questo potrebbe trasformarsi in un sacco di boilerplate. Molti riduttori che ho realizzato possono diventare molto più complessi di così, con un sacco di codice ridondante. E se potessimo mettere insieme tutte le semplici azioni di modifica della proprietà?

Si scopre che è facile:

Anche con la spaziatura extra e il commento extra, questa versione è più breve - e questo è solo due casi. I risparmi possono davvero sommare.

Il passaggio ... non è pericoloso? Vedo una caduta!

Potresti aver letto da qualche parte che le dichiarazioni `switch` dovrebbero essere evitate, in particolare in modo da evitare cadute accidentali e perché l'elenco dei casi può gonfiarsi. Potresti aver sentito che non dovresti mai usare la caduta intenzionale, perché è difficile catturare bug accidentali. È un buon consiglio, ma pensiamo attentamente ai pericoli che ho menzionato sopra:

  • I riduttori sono componibili, quindi il gonfiore del case non è un problema. Se l'elenco dei casi diventa troppo grande, rompere i pezzi e spostarli in riduttori separati.
  • Ogni corpo del caso ritorna, quindi una caduta accidentale non dovrebbe mai accadere. Nessuno dei casi raggruppati dovrebbe avere corpi diversi da quello che esegue la cattura.

Redux usa bene `switch..case`. Sto cambiando ufficialmente il mio consiglio in merito. Fintanto che segui le semplici regole sopra (mantieni gli interruttori piccoli e focalizzati, e ritorna da ogni caso con il suo stesso corpo), le istruzioni `switch` vanno bene.

Potresti aver notato che questa versione richiede un payload diverso. Qui è dove arrivano i tuoi creatori di azioni:

Come puoi vedere, questi creatori di azioni stanno facendo la traduzione tra gli argomenti e la forma dello stato. Ma non è tutto quello che stanno facendo ...

8. Utilizzare i valori predefiniti dei parametri ES6 per la documentazione della firma

Se stai usando Tern.js con un plug-in editor (disponibile per editor popolari come Sublime Text e Atom), leggerà quei compiti ES6 predefiniti e inferirà l'interfaccia richiesta dei tuoi creatori di azioni, quindi quando li chiami, puoi ottenere intellisense e completamento automatico. Ciò toglie il carico cognitivo agli sviluppatori, perché non dovranno ricordare il tipo di payload richiesto o controllare il codice sorgente quando dimenticano.

Se non stai utilizzando un plug-in di inferenza del tipo come Tern, TypeScript o Flow, dovresti esserlo.

Nota: preferisco fare affidamento sull'inferenza fornita dalle assegnazioni predefinite visibili nella firma della funzione rispetto alle annotazioni del tipo, perché:

  1. Non è necessario utilizzare Flow o TypeScript per farlo funzionare: invece si utilizza JavaScript standard.
  2. Se si utilizza TypeScript o Flow, le annotazioni sono ridondanti con le assegnazioni predefinite, poiché sia ​​TypeScript che Flow deducono il tipo dall'assegnazione predefinita.
  3. Lo trovo molto più leggibile quando c'è meno rumore di sintassi.
  4. Ottieni impostazioni predefinite, il che significa che, anche se non stai interrompendo la compilazione di CI su errori di tipo (saresti sorpreso, molti progetti no), non avrai mai un parametro accidentale `indefinito` in agguato nel tuo codice.

9. Utilizzare i selettori per stato calcolato e disaccoppiamento

Immagina di creare l'app di chat più complessa nella storia delle app di chat. Hai scritto 500k righe di codice e POI il team del prodotto ti lancia un nuovo requisito di funzionalità che ti costringerà a cambiare la struttura dei dati del tuo stato.

Non c'è bisogno di andare nel panico. Sei stato abbastanza intelligente da separare il resto dell'app dalla forma del tuo stato con i selettori. Proiettile: schivato.

Per quasi tutti i riduttori che scrivo, creo un selettore che esporta semplicemente tutte le variabili di cui ho bisogno per costruire la vista. Vediamo come potrebbe essere il nostro semplice riduttore di chat:

export const getViewState = state => state;

Si lo so. È così semplice che non vale nemmeno la pena dare un'occhiata. Potresti pensare che ora sono pazzo, ma ricordi quel proiettile che abbiamo schivato prima? E se volessimo aggiungere uno stato calcolato, come un elenco completo di tutti gli utenti che hanno chattato durante questa sessione? Chiamiamolo "recentActiveUsers".

Queste informazioni sono già memorizzate nel nostro stato attuale, ma non in un modo facile da afferrare. Andiamo avanti e prendiamolo in `getViewState ()`:

Se metti tutto il tuo stato calcolato nei selettori, tu:

  1. Riduci la complessità dei tuoi riduttori e componenti
  2. Disaccoppia il resto dell'app dalla forma del tuo stato
  3. Obbedisci al principio della singola fonte di verità, anche all'interno del tuo riduttore

10. Utilizzare TDD: scrivere prima i test

Molti studi hanno confrontato test-first con metodologie test-after e nessun test. I risultati sono chiari e drammatici: la maggior parte degli studi mostra una riduzione del 40-80% nei bug di spedizione a seguito della stesura di test prima dell'implementazione delle funzionalità.

TDD può dimezzare efficacemente la densità dei bug di spedizione e ci sono molte prove a sostegno di tale affermazione.

Durante la scrittura degli esempi in questo articolo, ho iniziato tutti con test unitari.

Per evitare test fragili, ho creato le seguenti fabbriche che usavo per produrre aspettative:

Si noti che entrambi forniscono valori predefiniti, il che significa che posso sovrascrivere le proprietà individualmente per creare solo i dati a cui sono interessato per un determinato test.

Ecco come li ho usati:

Nota: utilizzo nastro per test unitari per la sua semplicità. Ho anche 2-3 anni di esperienza con Moka e Gelsomino e varie esperienze con molti altri framework. Dovresti essere in grado di adattare questi principi a qualunque framework tu scelga.

Nota lo stile che ho sviluppato per descrivere i test nidificati. Probabilmente a causa del mio background con Jasmine e Mocha, mi piace iniziare descrivendo il componente che sto testando in un blocco esterno, quindi nei blocchi interni, descrivendo ciò che sto passando al componente. All'interno, faccio semplici asserzioni di equivalenza che puoi fare con le funzioni `deepEqual ()` o `toEqual ()` della tua libreria di test.

Come puoi vedere, utilizzo lo stato di test isolato e le funzioni di fabbrica invece di utility come `beforeEach ()` e `afterEach ()`, che evito perché possono incoraggiare gli sviluppatori inesperti a utilizzare lo stato condiviso nella suite di test (è un male) .

Come probabilmente hai indovinato, ho tre diversi tipi di test per ciascun riduttore:

  1. Test diretti del riduttore, di cui hai appena visto un esempio. Questi essenzialmente testano che il riduttore produce lo stato predefinito previsto.
  2. Test del creatore di azioni, che testano ciascun creatore di azioni applicando il riduttore all'azione utilizzando uno stato predeterminato come punto di partenza.
  3. Test del selettore, che verifica i selettori per garantire che siano presenti tutte le proprietà previste, comprese le proprietà calcolate con valori previsti.

Hai già visto un test di riduzione. Diamo un'occhiata ad altri esempi.

Test del creatore di azioni

Questo esempio è interessante per un paio di ragioni. Il creatore di azioni `addChat ()` non è puro. Ciò significa che, a meno che non si passi le sostituzioni di valore, non è possibile fare un'aspettativa specifica per tutte le proprietà prodotte. Per far questo, abbiamo usato una pipe, che a volte uso per evitare di creare variabili extra di cui non ho davvero bisogno. L'ho usato per ignorare i valori generati. Ci assicuriamo ancora che esistano, ma non ci interessa quali siano i valori. Nota che non sto nemmeno controllando il tipo. Ci fidiamo dell'inferenza del tipo e dei valori predefiniti per occuparcene.

Una pipe è un'utilità funzionale che ti consente di trasferire un certo valore di input attraverso una serie di funzioni che ciascuna prende l'output della funzione precedente e lo trasforma in qualche modo. Io uso lodash pipe da `lodash / fp / pipe`, che è un alias di` lodash / flow`. È interessante notare che `pipe ()` stesso può essere creato con una funzione di riduzione:

Tendo ad usare molto `pipe ()` anche nei file del riduttore per semplificare le transizioni di stato. Tutte le transizioni di stato sono in definitiva flussi di dati che si spostano da una rappresentazione di dati a quella successiva. Questo è ciò che `pipe ()` è bravo a.

Nota che il creatore dell'azione ci consente di sovrascrivere anche tutti i valori predefiniti, in modo da poter passare ID e timestamp specifici e testare valori specifici.

Test di selezione

Infine, testiamo i selettori di stato e ci assicuriamo che i valori calcolati siano corretti e che tutto sia come dovrebbe essere:

Nota che in questo test abbiamo usato `Array.prototype.reduce ()` per ridurre alcune azioni `addChat ()` di esempio. Una delle cose grandiose dei riduttori Redux è che sono solo normali funzioni del riduttore, il che significa che puoi fare qualsiasi cosa con loro come faresti con qualsiasi altra funzione del riduttore.

Il nostro valore "previsto" verifica che tutti i nostri oggetti di chat siano nel registro e che gli utenti attivi di recente siano elencati correttamente.

Non c'è molto altro da dire al riguardo.

Redux Rules

Se usi Redux correttamente, otterrai grandi vantaggi:

  • Elimina i bug di dipendenza del timing
  • Abilita rendering della vista deterministica
  • Abilita la riproduzione dello stato deterministico
  • Abilita le funzioni di annullamento / ripetizione facili
  • Semplifica il debug
  • Diventa un viaggiatore nel tempo

Ma per far funzionare tutto ciò, devi ricordare alcune regole:

  • I riduttori devono essere funzioni pure
  • I riduttori devono essere l'unica fonte di verità per il loro stato
  • Lo stato del riduttore deve essere sempre serializzabile
  • Lo stato del riduttore non deve contenere funzioni

Tieni anche presente:

  • Alcune app non necessitano di Redux
  • Usa le costanti per i tipi di azione
  • Utilizzare i creatori di azioni per separare la logica di azione dai chiamanti di invio
  • Utilizzare le impostazioni predefinite dei parametri ES6 per le firme auto-descrittive
  • Utilizzare i selettori per lo stato calcolato e il disaccoppiamento
  • Usa sempre TDD!

Godere!

Sei pronto a far salire di livello le tue abilità Redux con DevAnywhere?

Scopri la programmazione funzionale avanzata, React e Redux con tutoraggio 1: 1. Membri di Lifetime Access, controlla la programmazione funzionale e le lezioni di Redux. Assicurati di guardare la serie Shotgun e guidare il fucile con me mentre costruisco app reali con React e Redux.

https://devanywhere.io/

Eric Elliott è autore di "Programmazione di applicazioni JavaScript" (O’Reilly) e cofondatore di DevAnywhere.io. Ha contribuito alle esperienze software per Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC e i migliori artisti della registrazione tra cui Usher, Frank Ocean, Metallica e molti altri.

Lavora dove vuole con la donna più bella del mondo.