Comprensione di Redux: la guida più semplice al mondo per iniziare Redux

Questa è una guida completa (ma semplificata) per i principianti assoluti di Redux o per chiunque voglia rivalutare la propria comprensione dei concetti fondamentali di Redux.

Per un sommario ampliato, visitare questo link e per concetti Redux più avanzati, consultare i miei libri Redux.

introduzione

Questo articolo (che in realtà è un libro) è il pezzo mancante se hai cercato a lungo come padroneggiare Redux.

Prima di iniziare, dovrei dirti che il libro è prima di tutto su di me. Si, io. Le mie difficoltà con l'apprendimento di Redux e la ricerca di un modo migliore per insegnarlo.

Alcuni anni fa, avevo appena imparato React. Ne ero entusiasta, ma ancora una volta, tutti gli altri sembravano parlare di qualcos'altro chiamato Redux.

Perbacco! La sequenza di apprendimento finisce mai?

Come ingegnere impegnato nel mio sviluppo personale, volevo essere al corrente. Non volevo essere lasciato fuori. Quindi, ho iniziato a imparare Redux.

Ho controllato la documentazione di Redux. È stato abbastanza buono, in realtà! Per qualche motivo, non ha fatto clic per me. Ho controllato anche un sacco di video di YouTube. Quelli che ho trovato sembravano affrettati e non dettagliati. Povero me.

Onestamente, non credo che i tutorial video che ho visto fossero cattivi. Mancava solo qualcosa. Una guida facile che è stata ben pensata e scritta per una persona sana come me e non per un umanoide immaginario.

Sembrava che non fossi solo.

Un mio caro amico, qualcuno che stavo facendo da mentore in quel momento, aveva appena completato un corso di certificazione per sviluppatori React in cui pagava un sacco di soldi (oltre $ 300) per guadagnare un certificato.

Quando ho chiesto il suo onesto feedback sul programma, le sue parole erano sulla falsariga di:

Il corso è stato abbastanza buono, ma non credo ancora che Redux sia stato ben spiegato a un principiante come me. Non è stato spiegato bene.

Vedi, ce ne sono molti altri come il mio amico, tutti faticano a capire Redux. Forse usano Redux, ma non possono dire di aver veramente capito come funziona.

Ho deciso di trovare una soluzione. Avrei capito profondamente Redux e avrei trovato un modo più chiaro per insegnarlo.

Quello che stai per leggere ha richiesto mesi di studio, e poi ancora un po 'di tempo per scrivere e sviluppare i progetti di esempio, il tutto mantenendo un lavoro quotidiano e altri impegni seri.

Ma sai una cosa?

Sono super entusiasta di condividere questo con te!

Se hai cercato una guida Redux che non ti parlerà, è così. Non guardare oltre.

Ho preso in considerazione le mie lotte e quelle di molti altri che conosco. Mi assicurerò di insegnarti le cose importanti e di farlo senza farti confondere.

Questa è una promessa.

Il mio approccio all'insegnamento di Redux

Il vero problema con l'insegnamento di Redux - specialmente per i principianti - non è la complessità della biblioteca stessa di Redux.

No. Non penso sia quello. È solo una piccola libreria da 2kb - comprese le dipendenze.

Dai un'occhiata alla community di Redux come principiante e perderai rapidamente la testa. Non c'è solo Redux, ma molte altre presunte "librerie associate" sono necessarie per creare app del mondo reale.

Se hai trascorso un po 'di tempo a fare un po' di ricerca, allora li hai già incontrati. C'è Redux, React-Redux, Redux-thunk, Redux-saga, Redux-promise, Reselect, Recompose e molti altri!

Come se ciò non bastasse, ci sono anche alcuni routing, autenticazione, rendering lato server, test e raggruppamento sparsi su di esso - tutto in una volta.

Perbacco! È travolgente.

Il "tutorial Redux" spesso non riguarda tanto Redux, ma tutte le altre cose che ne derivano.

Deve esserci un approccio più sano su misura per i principianti. Se sei uno sviluppatore umanoide, sicuramente non avresti problemi con questo. Indovina un po? Molti di noi sono in realtà umani.

Quindi, ecco il mio approccio all'insegnamento di Redux.

Dimentica tutte le cose extra per un po 'e facciamo solo Redux. Si!

Presenterò solo il minimo indispensabile per il momento. Non ci saranno React-router, Redux-form, Reselect, Ajax, Webpack, Authentication, Testing, nessuno di questi - per ora!

E indovina cosa? È così che hai imparato a fare alcune delle importanti "abilità" di vita che hai.

Come hai imparato a camminare?

Hai iniziato a correre in un giorno? No!

Lascia che ti guidi attraverso un approccio sano all'apprendimento del Redux, senza problemi.

Siediti bene.

“Una marea crescente solleva tutte le barche”

Una volta capito come funzionano le basi di Redux (la marea crescente), tutto il resto sarà più facile da ragionare (solleva tutte le barche).

Una nota sulla curva di apprendimento di Redux

Un Tweet sulla curva di apprendimento di Redux di Eric Elliot.

Redux ha una curva di apprendimento. Non sto dicendo il contrario.

Imparare a camminare aveva anche una curva di apprendimento. Tuttavia, con un approccio sistematico all'apprendimento, lo hai superato.

Sei caduto un paio di volte, ma andava bene. Qualcuno era sempre in giro per trattenerti e aiutarti ad alzarti.

Bene, spero di essere quella persona per te - mentre impari Redux con me.

Cosa imparerai

Dopo che tutto è stato detto e fatto, arriverai a vedere che Redux non è così spaventoso come sembra dall'esterno.

I principi sottostanti sono così dannatamente facili!

Prima di tutto, ti insegnerò i fondamenti di Redux in un linguaggio semplice e di facile approccio.

Quindi, creeremo alcune semplici applicazioni. A partire da un'app Hello World di base.

Un'applicazione Hello World Redux di base.

Ma quelli non saranno sufficienti.

Includerò esercizi e problemi che penso dovresti affrontare anche tu.

App di allenamento di esempio su cui lavoreremo insieme.

L'apprendimento efficace non riguarda solo la lettura e l'ascolto. L'apprendimento efficace riguarda principalmente la pratica!

Pensa a questi come compiti a casa, ma senza l'insegnante arrabbiato. Mentre pratichi gli esercizi, puoi Tweet me con l'hashtag #UnderstandingRedux e sicuramente darò un'occhiata!

Nessun insegnante arrabbiato, eh?

Gli esercizi sono buoni, ma devi anche guardarmi mentre costruisco un'applicazione più grande. Qui è dove finiamo le cose costruendo Skypey, un'app di messaggistica dolce come un clone di Skype.

Skypey: il clone di Skype che costruiremo insieme.

Skypey ha funzionalità come la modifica dei messaggi, l'eliminazione di messaggi e l'invio di messaggi a più contatti.

Evviva!

Se questo non ti ha entusiasmato, non so cosa lo farà. Sono super entusiasta di mostrarti questi!

Gif di Jona Dinges

necessario

L'unico prerequisito è che conosci già React. Se non lo fai, il Pure React di Dave Ceddia è il mio consiglio personale se hai qualche soldo da risparmiare. Non sono un affiliato. È solo una buona risorsa.

Scarica PDF ed Epub per la lettura offline

Il video seguente evidenzia il processo necessario per ottenere le versioni PDF ed Epub del libro.

Il punto cruciale è questo:

  1. Visita la pagina di vendita del libro.
  2. Usa il coupon FREECODECAMP per ottenere il 100% di sconto sul prezzo in modo da ottenere un libro da $ 29 per $ 0.
  3. Se vuoi dire grazie, ti preghiamo di raccomandare questo articolo: fai clic e tieni premuta l'icona di applauso media. +50.

Ora cominciamo.

Capitolo 1: Conoscere Redux

Alcuni anni fa, lo sviluppo di applicazioni front-end sembrava uno scherzo per molti. In questi giorni, la crescente complessità della creazione di applicazioni front-end decenti è quasi schiacciante.

Sembra che per soddisfare le pressanti esigenze dell'utente sempre esigente, il simpatico gatto carino ha invaso i confini di una casa. È diventato un leone senza paura con artigli da 3 pollici e una bocca che si apre abbastanza da adattarsi a una testa umana.

Sì, questo è ciò che sembra lo sviluppo moderno del front-end in questi giorni.

Quadri moderni come Angular, React e Vue hanno fatto un ottimo lavoro nel domare questa "bestia". Allo stesso modo, esistono filosofie moderne come quelle imposte da Redux per dare a questa "bestia" una pillola fredda.

Segui mentre diamo un'occhiata a queste filosofie.

Che cos'è Redux?

Che cos'è Redux? Come visto nella documentazione di Redux

La documentazione ufficiale per Redux recita:

Redux è un contenitore di stato prevedibile per le app JavaScript.

Quelle 9 parole sembravano 90 frasi incomplete quando le ho lette per la prima volta. Non l'ho capito. Molto probabilmente neanche tu.

Non sudare. Lo esaminerò tra poco, e man mano che usi Redux di più, quella frase diventerà più chiara.

Il lato positivo, se leggi la documentazione un po 'più a lungo, troverai le cose più esplicative da qualche parte lì dentro.

Si legge:

Ti aiuta a scrivere applicazioni che si comportano in modo coerente ...

Lo vedi?

In parole povere, sta dicendo "ti aiuta a domare la bestia". Metaforicamente.

Redux elimina alcune delle seccature dovute alla gestione dello stato in grandi applicazioni. Ti offre una straordinaria esperienza di sviluppo e garantisce che la testabilità della tua app non venga sacrificata per nessuno di questi.

Mentre sviluppi applicazioni React, potresti scoprire che mantenere tutto il tuo stato in un componente di livello superiore non è più sufficiente per te.

È inoltre possibile che nel tempo vengano modificati molti dati nell'applicazione.

Redux aiuta a risolvere questo tipo di problemi. Intendiamoci, non è l'unica soluzione là fuori.

Perché usare Redux?

Come già sapete, domande come "Perché dovresti usare A su B?" Si riducono alle tue preferenze personali.

Ho creato app in produzione che non usano Redux. Sono sicuro che molti hanno fatto lo stesso.

Per me, ero preoccupato di introdurre un ulteriore livello di complessità per i membri del mio team. Nel caso ti stia chiedendo, non mi pento affatto della decisione.

L'autore di Redux, Dan Abamov, avverte anche del pericolo di introdurre Redux troppo presto nella tua domanda. Potrebbe non piacerti Redux, e questo è abbastanza giusto. Ho amici che non lo fanno.

Detto questo, ci sono ancora alcune ragioni decenti per imparare il Redux.

Ad esempio, nelle app più grandi con molti pezzi in movimento, la gestione dello stato diventa una grande preoccupazione. Redux lo spunta abbastanza bene senza problemi di prestazioni o negoziabilità della testabilità.

Un'altra ragione per cui molti sviluppatori adorano Redux è l'esperienza degli sviluppatori che ne deriva. Molti altri strumenti hanno iniziato a fare cose simili, ma grandi crediti per Redux.

Alcune delle cose carine che ottieni con l'utilizzo di Redux includono la registrazione, la ricarica a caldo, i viaggi nel tempo, le app universali, la registrazione e la riproduzione, il tutto senza fare tanto da parte dello sviluppatore. Queste cose probabilmente suoneranno fantasiose finché non le usi e le vedi da solo.

Il discorso di Dan chiamato Hot Reloading con Time Travel ti darà un'idea di come funzionano.

Inoltre, Mark Ericsson, uno dei manutentori di Redux, afferma che oltre il 60% delle app React in produzione utilizza Redux. Questo è molto!

Di conseguenza, e questo è solo il mio pensiero, a molti ingegneri piace mostrare ai potenziali datori di lavoro che possono mantenere basi di codice di produzione più grandi costruite in React e Redux, così imparano Redux.

Se vuoi altri motivi per usare Redux, Dan, il creatore di Redux, ha alcuni altri motivi evidenziati nel suo articolo su Medium.

Se non ti consideri un ingegnere senior, ti consiglio di imparare Redux, in gran parte grazie ad alcuni dei principi che insegna. Imparerai nuovi modi di fare cose comuni e questo probabilmente ti renderà un ingegnere migliore.

Ognuno ha ragioni diverse per raccogliere tecnologie diverse. Alla fine, la chiamata è tua. Ma sicuramente non fa male aggiungere Redux al tuo set di abilità.

Spiegare Redux a un bambino di 5 anni

Questa sezione del libro è davvero importante. La spiegazione qui sarà citata in tutto il libro. Quindi preparati.

Dato che un bambino di 5 anni non ha tempo per il gergo tecnico, lo terrò molto semplice ma pertinente al nostro scopo di imparare il Redux.

Quindi, eccoci qui!

Consideriamo un evento che probabilmente conoscerai: andare in banca per prelevare contanti. Anche se non lo fai spesso, probabilmente sei consapevole dell'aspetto del processo.

Il giovane Joe si dirige verso la banca.

Ti svegli una mattina e vai in banca il più rapidamente possibile. Mentre vai in banca c'è solo un'intenzione / azione che hai in mente: a WITHDRAW_MONEY.

Vuoi prelevare denaro dalla banca.

Il giovane Joe si dirige verso la banca con l'intenzione di ritirare del denaro.

Ecco dove le cose si fanno interessanti.

Quando entri in banca, vai direttamente alla Cassa per rendere nota la tua richiesta.

Il giovane Joe è in banca! Va dritto a vedere la Cassa e fa conoscere la sua richiesta.

Aspetta, sei andato alla Cassa?

Perché non sei andato nel caveau di una banca per ottenere i tuoi soldi?

Se solo il giovane Joe fosse entrato nel Vault. Andrà via con tutto ciò che trova.

Dopo tutto, sono i tuoi soldi guadagnati duramente.

Bene, come già sai, le cose non funzionano in questo modo. Sì, la banca ha dei soldi nel caveau, ma devi parlare con il Cassiere per aiutarti a seguire una procedura adeguata per ritirare i tuoi soldi.

Il Cassiere, dal loro computer, quindi inserisce alcuni comandi e ti consegna i tuoi soldi. Vai tranquillo.

Ecco come ottieni denaro. Non dal Vault, scusa.

Ora, come si inserisce Redux in questa storia?

Presto arriveremo a maggiori dettagli, ma prima di tutto la terminologia.

1. Il Bank Vault è per la banca ciò che il Redux Store è per Redux.

Il caveau di una banca può essere paragonato al Redux Store!

Il caveau della banca conserva i soldi in banca, giusto?

Bene, all'interno della tua applicazione, non spendi soldi. Invece, lo stato della tua applicazione è come il denaro che spendi. L'intera interfaccia utente dell'applicazione è una funzione del tuo stato.

Proprio come il caveau di una banca mantiene i tuoi soldi al sicuro in banca, lo stato della tua applicazione è tenuto al sicuro da qualcosa chiamato negozio. Quindi, il negozio mantiene intatti i tuoi "soldi" o il tuo stato.

Uh, devi ricordartelo, ok?

Il Redux Store può essere paragonato a Bank Vault. Mantiene lo stato della tua applicazione e la mantiene al sicuro.

Questo porta al primo principio Redux:

Avere un'unica fonte di verità: lo stato dell'intera applicazione è archiviato in un albero degli oggetti all'interno di un singolo archivio Redux.

Non lasciare che le parole ti confondano.

In termini semplici, con Redux, è consigliabile memorizzare lo stato dell'applicazione in un singolo oggetto gestito dall'archivio Redux. È come avere una volta che si oppone alla sporcizia di denaro ovunque lungo la sala della banca.

Il primo Redux Princple

2. Vai in banca con in mente un'azione.

Se hai intenzione di ottenere denaro dalla banca, dovrai entrare con qualche intenzione o azione per prelevare denaro.

Se entri in banca e ti aggiri, nessuno ti darà soldi. Potresti anche essere stato buttato fuori dalla sicurezza. Roba triste.

Lo stesso si può dire per Redux.

Scrivi tutto il codice che vuoi, ma se vuoi aggiornare lo stato della tua applicazione Redux (come fai con setState in React), devi farlo sapere a Redux con un'azione.

Allo stesso modo in cui segui una procedura per ritirare i tuoi soldi dalla banca, Redux rappresenta anche una procedura per cambiare / aggiornare lo stato della tua domanda.

Questo porta al principio Redux n. 2.

Lo stato è di sola lettura:
L'unico modo per cambiare lo stato è emettere un'azione, un oggetto che descriva ciò che è accaduto.

Cosa significa in parole povere?

Quando cammini verso la riva, ci vai con un'azione chiara in mente. In questo esempio, vuoi prelevare del denaro.

Se scegliamo di rappresentare quel processo in una semplice applicazione Redux, la tua azione in banca potrebbe essere rappresentata da un oggetto.

Uno che assomiglia a questo:

{
  tipo: "WITHDRAW_MONEY",
  importo: "$ 10.000"
}

Nel contesto di un'applicazione Redux, questo oggetto è chiamato azione! Ha sempre un campo del tipo che descrive l'azione che si desidera eseguire. In questo caso, è WITHDRAW_MONEY.

Ogni volta che è necessario modificare / aggiornare lo stato dell'applicazione Redux, è necessario inviare un'azione.

Il secondo principio Redux.

Non insistere su come farlo ancora. Sto solo gettando le basi qui. Presto approfondiremo molti esempi.

3. La Cassa è per la banca ciò che il riduttore è per Redux.

Va bene, fai un passo indietro.

Ricorda che nella storia sopra, non puoi semplicemente andare direttamente nel caveau della banca per recuperare i tuoi soldi dalla banca. No. Prima dovevi vedere la Cassa.

Bene, avevi in ​​mente un'azione, ma dovevi trasmettere quell'azione a qualcuno - il Cassiere - che a sua volta comunicava (in qualunque modo lo facessero) con il caveau che conteneva tutto il denaro della banca.

La comunicazione Cassiere e Vault!

Lo stesso si può dire per Redux.

Come hai reso nota la tua azione alla Cassa, devi fare lo stesso nella tua applicazione Redux. Se vuoi aggiornare lo stato della tua applicazione, trasmetti la tua azione al riduttore, il nostro Cassiere.

Questo processo è principalmente chiamato invio di un'azione.

Spedizione è solo una parola inglese. In questo esempio, e nel mondo Redux, viene utilizzato per inviare l'azione ai riduttori.

Il riduttore sa cosa fare. In questo esempio, porterà la tua azione a WITHDRAW_MONEY e ti assicurerà di ottenere i tuoi soldi.

In termini Redux, il denaro che spendi è il tuo stato. Quindi, il tuo riduttore sa cosa fare e restituisce sempre il tuo nuovo stato.

Hmmm. Non è stato così difficile da capire, giusto?

E questo porta all'ultimo principio Redux:

Per specificare come l'albero degli stati viene trasformato da azioni, scrivi riduttori puri.

Mentre procediamo, spiegherò cosa significa un riduttore "puro". Per ora, l'importante è capire che, per aggiornare lo stato della tua applicazione (come fai con setState in React), le tue azioni devono sempre essere inviate (inviate) ai riduttori per ottenere il tuo nuovo stato.

Il terzo principio Redux

Con questa analogia, ora dovresti avere un'idea di quali siano gli attori Redux più importanti: il negozio, il riduttore e un'azione.

Questi tre attori sono fondamentali per qualsiasi applicazione Redux. Una volta capito come funzionano, la maggior parte dell'atto è fatta.

Capitolo 2: La tua prima applicazione Redux

Impariamo dall'esempio e dall'esperienza diretta perché ci sono limiti reali all'adeguatezza dell'istruzione verbale.
Malcom Gladwell

Anche se ho trascorso molto tempo a spiegare i principi di Redux in un modo che non dimenticherai, le istruzioni verbali hanno i loro limiti.

Per approfondire la tua comprensione dei principi, ti mostrerò un esempio. La tua prima applicazione Redux, se vuoi chiamarla così.

Il mio approccio all'insegnamento è introdurre esempi di difficoltà crescente. Quindi, per cominciare, questo esempio è focalizzato sul refactoring di una semplice app React pura per usare Redux.

Lo scopo qui è capire come introdurre Redux in un semplice progetto React e approfondire anche la comprensione dei concetti fondamentali di Redux.

Pronto?

Di seguito è la banale app React "Hello World" con cui lavoreremo.

L'app Hello World di base.

Non ridere.

Imparerai a flettere i tuoi muscoli Redux da un concetto "noto" come React, al "sconosciuto" Redux.

La struttura dell'applicazione React Hello World

L'app React con cui lavoreremo è stata avviata con l'app create-reagire. Pertanto, la struttura dell'app è quella a cui sei già abituato.

Puoi prendere il repository da Github se vuoi seguire, cosa che raccomando.

Esiste un file di voce index.js che esegue il rendering di un componente nel DOM.

Il componente principale dell'app è costituito da un determinato componente .

Questo componente accetta un puntello tecnologico e questo prop è responsabile della particolare tecnologia mostrata all'utente.

Ad esempio, produrrà quanto segue:

L'app Hello World di base con lo stato predefinito

Inoltre, un produrrà quanto segue.

L'app Hello World di base con l'elica tecnologica è cambiata in

Ora hai capito.

Ecco come appare il componente App:

src / App.js

import React, {Component} da "reagire";
importare HelloWorld da "./HelloWorld";

class App estende Component {
 stato = {
  tecnologia: "Reagisci"
}
render () {
  return 
}
}

esportazione app predefinita;

Dai un'occhiata all'oggetto stato.

C'è solo un campo, la tecnologia, nell'oggetto stato e viene passato come prop nel componente HelloWorld come mostrato di seguito:

Non preoccuparti ancora dell'implementazione del componente HelloWorld. Accetta solo un oggetto tecnologico e applica alcuni CSS fantasiosi. È tutto.

Dal momento che questo si concentra principalmente su Redux, salterò i dettagli dello stile.

Quindi, ecco la sfida.

In che modo riutilizziamo la nostra App per utilizzare Redux?

Come eliminiamo l'oggetto stato e lo gestiamo interamente da Redux? Ricorda che Redux è il gestore dello stato della tua app.

Cominciamo a rispondere a queste domande nella prossima sezione.

Rivisitare la tua conoscenza di Redux

Ricordi la citazione dai documenti ufficiali?

Redux è un contenitore di stato prevedibile per le app JavaScript.

Una frase chiave nella frase sopra è contenitore dello stato.

Tecnicamente, vuoi che lo stato della tua applicazione sia gestito da Redux.

Questo è ciò che rende Redux un contenitore di stato.

Lo stato del componente React esiste ancora. Redux non lo toglie.

Tuttavia, Redux gestirà in modo efficiente lo stato generale dell'applicazione. Come un caveau di una banca, ha un negozio per farlo.

Per il semplice componente che abbiamo qui, l'oggetto stato è semplice.

Ecco qui:

{
 tecnologia: "Reagisci"
}

Dobbiamo toglierlo dallo stato del componente e gestirlo da Redux.

Dalla mia precedente spiegazione, dovresti ricordare l'analogia tra Bank Vault e Redux Store. Bank Vault conserva denaro, il negozio Redux mantiene l'oggetto stato dell'applicazione.

Quindi, qual è il primo passo per il refactoring del componente per usare Redux?

Sì, hai capito bene.

Rimuovere lo stato del componente da .

Il negozio Redux sarà responsabile della gestione dello stato dell'app. Detto questo, dobbiamo rimuovere l'oggetto stato corrente da App />.

import React, {Component} da "reagire";
importare HelloWorld da "./HelloWorld";

class App estende Component {
 // l'oggetto stato è stato rimosso.
render () {
  return 
}
}

esportazione app predefinita;

La soluzione sopra è incompleta, ma al momento non ha stato.

Installa Redux eseguendo filato aggiungi redux dall'interfaccia della riga di comando (CLI). Abbiamo bisogno del pacchetto redux per fare qualsiasi cosa nel modo giusto.

Creazione di un negozio Redux

Se la non riesce a gestirlo, allora dobbiamo creare un Redux Store per gestire lo stato della nostra applicazione.

Per un caveau di una banca, un paio di ingegneri meccanici furono probabilmente assunti per creare una struttura sicura per il mantenimento del denaro.

Per creare una struttura gestibile per lo stato per la nostra applicazione, non abbiamo bisogno di ingegneri meccanici. Lo faremo a livello di codice utilizzando alcune delle API che Redux ci fornisce.

Ecco come appare il codice per creare un negozio Redux:

importare {createStore} da "redux"; // un'importazione dalla libreria redux
const store = createStore (); // una soluzione incompleta - per ora.

Per prima cosa importiamo la funzione di fabbrica createStore da Redux. Quindi invochiamo la funzione, createStore () per creare il negozio.

Ora, la funzione createStore accetta alcuni argomenti. Il primo è un riduttore.

Quindi, una creazione del negozio più completa sarebbe rappresentata in questo modo: createStore (riduttore)

Ora, lasciami spiegare perché abbiamo un riduttore lì dentro.

La relazione Store and Reducer

Torna all'analogia della banca.

Quando vai in banca per effettuare un prelievo, incontri la Cassa. Dopo aver reso noto al Cassiere le tue intenzioni / azioni WITHDRAW_MONEY, non ti consegnano solo i soldi richiesti.

No.

Il Cassiere conferma innanzitutto che nel tuo account sono presenti fondi sufficienti per eseguire la transazione di prelievo richiesta.

Hai quanto vuoi addirittura ritirare?

Il Cassiere si assicura innanzitutto di avere i soldi che dici di fare.

Dal computer, possono vedere tutto questo - tipo di comunicazione con il Vault, poiché il Vault conserva tutti i soldi in banca.

In breve, la Cassa e il Vault sono sempre sincronizzati. Grandi amici!

La Cassa e il Vault sincronizzati!

Lo stesso si può dire per un Redux STORE (il nostro Vault) e il Redux REDUCER (il nostro Cassiere)

Il negozio e il riduttore sono grandi amici. Sempre sincronizzato.

Perché?

Il REDUCER "parla" sempre al NEGOZIO. Proprio come la Cassa rimane sincronizzata con il Vault.

Questo spiega perché la creazione del negozio deve essere invocata con un Riduttore, e questo è obbligatorio. Il riduttore è l'unico argomento obbligatorio passato a createStore ()

Il riduttore è un argomento obbligatorio passato a

Nella sezione seguente daremo una breve occhiata a Riduttori e quindi creeremo uno STORE passando il REDUCER nella funzione di fabbrica createStore.

The Reducer

Entreremo in maggiori dettagli molto presto, ma per ora lo terrò breve.

Quando senti la parola, riduttore, cosa ti viene in mente?

Ridurre?

Sì, è quello che ho pensato.

Sembra ridurre.

Bene, secondo i documenti ufficiali di Redux:

I riduttori sono il concetto più importante in Redux.
I riduttori sono il concetto più importante in Redux. Un engr più esperto. può discutere a favore dei middleware.

La nostra cassiera è una persona piuttosto importante, eh?

Quindi, qual è il problema con il riduttore. Che cosa fa?

In termini più tecnici, un riduttore è anche chiamato funzione riducente. Potresti non averlo notato, ma probabilmente usi già un riduttore, se conosci il metodo Array.reduce ().

Ecco un rapido aggiornamento.

Considera il codice qui sotto.

È un modo popolare per ottenere la somma di valori in un array JavaScript:

let arr = [1,2,3,4,5]
let sum = arr.reduce ((x, y) => x + y)
console.log (somma) // 15

Sotto il cofano, la funzione passata in arr.riduce si chiama riduttore.

In questo esempio, il riduttore accetta due valori, un accumulatore e un valore corrente, dove x è l'accumulatore e y è il valore corrente.

Allo stesso modo, il Redux Reducer è solo una funzione. Una funzione che accetta due parametri. Il primo è lo STATO dell'app e l'altro l'AZIONE.

Oh mio Dio! Ma da dove provengono STATO e AZIONE passati nel RIDUTTORE?

Quando stavo imparando Redux, mi sono posto alcune volte questa domanda.

Innanzitutto, dai nuovamente un'occhiata all'esempio Array.reduce ():

let arr = [1,2,3,4,5]
let sum = arr.reduce ((x, y) => x + y)
console.log (somma) // 15

Il metodo Array.reduce è responsabile del passaggio degli argomenti necessari, xey nell'argomento della funzione, il riduttore. Quindi, gli argomenti non sono venuti dal nulla.

Lo stesso si può dire per Redux.

Anche il riduttore Redux viene passato a un determinato metodo. Indovina di cosa si tratta?

Ecco qui!

createstore (riduttore)

La funzione di fabbrica createStore. C'è un po 'di più coinvolto nel processo, come vedrai presto.

Come Array.reduce (), createStore () è responsabile del passaggio degli argomenti nel riduttore.

Se non hai paura delle cose tecniche, ecco la versione ridotta dell'implementazione di createStore nel codice sorgente di Redux.

funzione createStore (riduttore) {
    stato var;
    var ascoltatori = []

    funzione getState () {
        stato di ritorno
    }
    
    funzione iscriviti (listener) {
        listeners.push (ascoltatore)
        return unsubscribe () {
            var index = hearers.indexOf (listener)
            hearers.splice (indice, 1)
        }
    }
    
    invio funzione (azione) {
        stato = riduttore (stato, azione)
        hearers.forEach (listener => listener ())
    }

    spedizione({})

    return {invia, iscriviti, getState}
}

Non picchiarti se non ottieni il codice sopra. Quello che voglio davvero sottolineare è all'interno della funzione di invio.

Notare come viene chiamato il riduttore con stato e azione

Detto questo, il codice minimo per la creazione di un negozio Redux è questo:

importare {createStore} da "redux";
const store = createStore (riduttore); // questo è stato aggiornato per includere il riduttore creato.

Tornare al processo di refactoring

Torniamo al refactoring dell'applicazione React "Hello World" per utilizzare Redux.

Se ti ho perso in qualsiasi momento della sezione precedente, leggi la sezione ancora una volta e sono sicuro che affonderà. Meglio ancora, puoi farmi una domanda.

Ok, quindi ecco tutto il codice che abbiamo a questo punto:

import React, {Component} da "reagire";
importare HelloWorld da "./HelloWorld";

 importare {createStore} da "redux";
 const store = createStore (riduttore);

 class App estende Component {
 render () {
   return 
 }
}

esportazione app predefinita;

Ha senso?

Potresti aver notato un problema con questo codice. Vedi linea 4.

La funzione di riduzione passata a createStore non esiste ancora.

Ora dobbiamo scriverne uno. Il riduttore è solo una funzione, ricordi?

Crea una nuova directory denominata riduttori e crea un file index.js al suo interno. In sostanza, la nostra funzione di riduzione sarà nel percorso src / riduttori / index.js.

Per prima cosa esporta una semplice funzione in questo file:

export default () => {
}

Ricordare che il riduttore accetta due argomenti, come stabilito in precedenza. In questo momento, ci occuperemo del primo argomento, STATE

Mettilo nella funzione e abbiamo questo:

export default (state) => {
}

Non male.

Un riduttore restituisce sempre qualcosa. Nell'esempio iniziale del riduttore Array.reduce (), abbiamo restituito la somma dell'accumulatore e il valore corrente.

Per un riduttore Redux, si restituisce sempre il nuovo stato dell'applicazione.

Lasciatemi spiegare.

Dopo che entri in banca e fai un prelievo riuscito, l'attuale importo di denaro conservato nel caveau della banca per te non è più lo stesso. Ora, se hai ritirato $ 200, ora sei a corto di $ 200. Il saldo del tuo account è sceso di $ 200.

Ancora una volta, la Cassa e il Vault rimangono sincronizzati su quanto hai ora.

Proprio come il cassiere, questo è esattamente come funziona il riduttore.

Come il cassiere, il riduttore restituisce sempre il nuovo stato dell'applicazione. Nel caso in cui qualcosa sia cambiato. Non vogliamo emettere lo stesso saldo bancario anche se è stata eseguita un'azione di prelievo.

Vedremo in seguito come modificare / aggiornare lo stato in seguito. Per ora, la fiducia cieca dovrà essere sufficiente.

Ora, torniamo al problema in questione.

Dal momento che non ci preoccupiamo di cambiare / aggiornare lo stato a questo punto, manterremo il nuovo stato restituito quando passerà lo stesso stato.

Ecco la rappresentazione di questo all'interno del riduttore:

export default (state) => {
stato di ritorno
}

Se vai in banca senza eseguire un'azione, il tuo saldo bancario rimane lo stesso, giusto?

Dal momento che non stiamo eseguendo alcuna AZIONE o che non viene ancora trasmessa nel riduttore, restituiremo semplicemente lo stesso stato.

Il secondo argomento createStore

Quando visiti la Cassa in banca, se hai chiesto loro il saldo del tuo conto, lo cercheranno e te lo diranno.

Quanto?

Ma come?

Quando hai creato un conto per la prima volta con la tua banca, l'hai fatto con una certa quantità di deposito oppure no.

Ho bisogno di un nuovo account con un deposito iniziale di $ 500

Chiamiamo questo il deposito iniziale nel tuo account.

Torna a Redux.

Allo stesso modo, quando crei un NEGOZIO redux (il nostro denaro mantenendo Vault), c'è la possibilità di farlo con un deposito iniziale.

In termini di Redux, questo si chiama initialState dell'app.

Pensando al codice, initialState è il secondo argomento passato alla chiamata della funzione createStore.

const store = createStore (riduttore, initialState);

Prima di effettuare qualsiasi azione monetaria, se hai richiesto il saldo del tuo conto bancario, il deposito iniziale ti verrà sempre restituito.

Successivamente, ogni volta che esegui un'azione monetaria, anche questo deposito iniziale verrà aggiornato.

Lo stesso vale per Redux.

L'oggetto passato come initialState è come il deposito iniziale nel Vault. Questo stato iniziale verrà sempre restituito come stato dell'applicazione a meno che non si aggiorni lo stato eseguendo un'azione.

Aggiorneremo ora l'applicazione per passare in uno stato iniziale:

const initialState = {tech: "React"};
const store = createStore (riduttore, initialState);

Nota come initialState è solo un oggetto ed è esattamente quello che avevamo come stato predefinito nell'app React prima di iniziare il refactoring.

Ora, ecco tutto il codice che abbiamo a questo punto - con il riduttore anche importato in App.

App.js

import React, {Component} da "reagire";
importare HelloWorld da "./HelloWorld";
riduttore di importazione da "./riduttori";
importare {createStore} da "redux";

const initialState = {tech: "React"};
const store = createStore (riduttore, initialState);

class App estende Component {
 render () {
   return 
 }
 }

esportazione app predefinita;

riduttori / index.js

export default state => {
stato di ritorno
}

Se stai programmando e provi a eseguire l'app ora, visualizzerai un errore. Perché?

Dai un'occhiata al supporto tecnico passato in . Si legge ancora, this.state.tech.

Non esiste più un oggetto stato associato a , quindi non sarà definito.

Risolviamolo.

La soluzione è abbastanza semplice Poiché il negozio ora gestisce lo stato della nostra applicazione, ciò significa che l'oggetto STATEobject deve essere recuperato dal negozio. Ma come?

Ogni volta che si crea un negozio con createStore (), il negozio creato ha tre metodi esposti.

Uno di questi è getState ().

In qualsiasi momento, la chiamata del metodo getState sull'archivio creato restituirà lo stato corrente dell'applicazione.

Nel nostro caso, store.getState () restituirà l'oggetto {tech: "React"} poiché questo è lo STATO INIZIALE che abbiamo passato nel metodo createStore () quando abbiamo creato lo STORE.

Vedi come tutto questo si riunisce ora?

Quindi l'elica tecnologica verrà passata in come mostrato di seguito:

App.js

import React, {Component} da "reagire";
importare HelloWorld da "./HelloWorld";
importare {createStore} da "redux";

const initialState = {tech: "React"};
const store = createStore (riduttore, initialState);

class App estende Component {
 render () {
   return 
 }
 }
Sostituisci

Riduttori / Reducer.js

export default state => {
stato di ritorno
}

E questo è tutto! Hai appena appreso le basi di Redux e riformattato con successo una semplice app React per utilizzare Redux.

L'applicazione React ora ha il suo stato gestito da Redux. Qualunque cosa debba essere ottenuta dall'oggetto stato verrà afferrata dal negozio come mostrato sopra.

Spero che tu abbia capito l'intero processo di refactoring.

Per una panoramica più rapida, dai un'occhiata a questo diff Github.

Con il progetto "Hello World", abbiamo esaminato alcuni concetti essenziali di Redux. Anche se è un progetto così piccolo, fornisce una base decente su cui costruire!

Possibile Gotcha

Nell'esempio di Hello World appena concluso, una possibile soluzione che potresti aver trovato per ottenere lo stato dal negozio potrebbe apparire così:

class App estende Component {
  state = store.getState ();
  render () {
    return ;
  }
}

Cosa ne pensi? Funzionerà?

Proprio come promemoria, i due modi seguenti sono modi corretti per inizializzare lo stato di un componente React.

(un)

class App estende Component {
 costruttore (oggetti di scena) {
   super (oggetti di scena);
   this.state = {}
  }
}

(B)

class App estende Component {
  stato = {}
}

Quindi, tornando a rispondere alla domanda, sì, la soluzione funzionerà perfettamente.

store.getState () prenderà lo stato corrente dal Redux STORE.

Tuttavia, l'assegnazione state = store.getState () assegnerà lo stato ottenuto da Redux a quello del componente .

Implicitamente, l'istruzione return da render come sarà valida.

Nota che questo legge this.state.tech non store.getState (). Tech.

Anche se funziona, è contro la filosofia ideale di Redux.

Se, all'interno dell'app, ora esegui this.setState (), lo stato dell'app verrà aggiornato senza l'aiuto di Redux.

Questo è il meccanismo di React predefinito e non è quello che vuoi. Volete che lo stato gestito dal Redux STORE sia l'unica fonte di verità.

Sia che tu stia recuperando lo stato, come in store.getState () o aggiornando / cambiando lo stato (come vedremo in seguito), vuoi che sia interamente gestito da Redux, non da setState ().

Dal momento che Redux gestisce lo stato dell'app, tutto ciò che devi fare è alimentare lo stato dal Redux STORE come oggetti di scena a qualsiasi componente richiesto.

Un'altra grande domanda che probabilmente ti stai ponendo è "Perché ho dovuto affrontare tutto questo stress solo per avere lo stato della mia app gestito da Redux?"

Reducer, Store, createStore blah, blah, blah ...

Sì, ho capito.

Anche io la pensavo così.

Tuttavia, considera il fatto che non vai semplicemente in banca e non segui una procedura adeguata per ritirare i tuoi soldi. Sono i tuoi soldi, ma devi seguire una procedura adeguata.

Lo stesso si può dire per Redux.

Redux ha il proprio "processo" per fare le cose. Dobbiamo imparare come funziona - ehi, non stai andando male!

Conclusione e sintesi

Questo capitolo è stato emozionante. Ci siamo concentrati principalmente sulla creazione di una base decente per le cose più interessanti a venire.

Ecco alcune cose che hai imparato in questo capitolo:

  • Redux è un contenitore di stato prevedibile per le app JavaScript.
  • La funzione di fabbrica createStore di Redux viene utilizzata per creare un Redux STORE.
  • Il riduttore è l'unico argomento obbligatorio passato a createStore ()
  • Un RIDUTTORE è solo una funzione. Una funzione che accetta due parametri. Il primo è lo STATO dell'app e l'altro è un'azione.
  • Un riduttore restituisce sempre il nuovo stato dell'applicazione.
  • Lo stato iniziale dell'applicazione, initialState è il secondo argomento passato nella chiamata della funzione createStore.
  • Store.getState () restituirà lo stato corrente dell'applicazione. Dove Store è un negozio Redux valido.

Presentazione degli esercizi

Per favore, per favore, per favore, non saltare gli esercizi. Soprattutto se non sei sicuro delle tue capacità di Redux e vuoi davvero ottenere il meglio da questa guida.

Quindi, prendi i tuoi cappelli di sviluppo e scrivi un po 'di codice :)

Inoltre, se vuoi che ti dia un feedback su una qualsiasi delle tue soluzioni in qualsiasi momento, inviami un tweet con l'hashtag #UnderstandingRedux e saremo felici di dare un'occhiata. Non sto promettendo di arrivare ad ogni singolo tweet, ma ci proverò sicuramente!

Una volta che avrai risolto gli esercizi, ti vedrò nella prossima sezione.

Ricorda che un buon modo per leggere contenuti lunghi è suddividerlo in bit digeribili più brevi. Questi esercizi ti aiutano a fare proprio questo. Ti prendi un po 'di tempo libero, provi a risolvere gli esercizi, poi torni a leggere. Questo è un modo efficace di studiare.

Vuoi vedere le mie soluzioni a questi esercizi? Ho incluso le soluzioni agli esercizi nel pacchetto del libro. Troverai le istruzioni su come ottenere il codice di accompagnamento e le soluzioni di allenamento dopo aver scaricato l'ebook (gratuito) (PDF ed Epub).

Quindi, ecco l'esercizio per questa sezione.

Esercizio

(a) Rifattorizzare l'app della carta utente per usare Redux

Nei file di codice di accompagnamento per il libro, troverai un'app per carte utente scritta esclusivamente in React. Lo stato dell'app è gestito tramite React. Il tuo compito è spostare lo stato in modo che sia gestito esclusivamente da Redux.

L'esercizio: l'app per tessere utente creata con React. Refactor per usare Redux.

Capitolo 3: Comprensione degli aggiornamenti di stato con azioni

Ora che abbiamo discusso i concetti di base di Redux, inizieremo a fare alcune cose più interessanti.

In questo capitolo, continueremo a imparare facendo mentre ti guido attraverso un altro progetto, spiegando ogni processo in dettaglio.

Quindi, su quale progetto lavorerà questa volta?

Ho quello perfetto.

Per favore, considera il modello qui sotto:

Design aggiornato dell'app Hello world.

Oh, sembra proprio come nell'esempio precedente, ma con alcune modifiche. Questa volta terremo conto delle azioni dell'utente. Quando facciamo clic su uno dei pulsanti, vogliamo aggiornare lo stato dell'applicazione come mostrato nella GIF di seguito:

La GIF!

Ecco come questo è diverso dall'esempio precedente. In questo scenario, l'utente sta eseguendo determinate azioni che influenzano lo stato dell'applicazione. Nell'esempio precedente, tutto ciò che abbiamo fatto è stato visualizzare lo stato iniziale dell'app senza prendere in considerazione le azioni dell'utente.

Cos'è un'azione Redux?

Quando entri in una banca, il Cassiere riceve la tua azione, cioè il tuo intento di entrare in banca. Nel nostro esempio precedente, è stato WITHDRAWAL_MONEY. L'unico modo in cui il denaro lascia la banca Vault è se fate la vostra azione o intento noti alla cassa.

Lo stesso vale per il Redux Reducer.

A differenza di setState () in React puro, l'unico modo per aggiornare lo stato di un'applicazione Redux è se si rende noto il proprio intento al REDUCER.

Ma come?

Inviando azioni!

Nel mondo reale, conosci l'azione esatta che vuoi eseguire. Probabilmente potresti scriverlo su un foglietto e consegnarlo alla Cassa.

Funziona quasi allo stesso modo con Redux. L'unica sfida è, come descrivi un'azione in un'app Redux? Sicuramente non parlando al banco o scrivendolo su una ricevuta.

Bene, ci sono buone notizie.

Un'azione è accuratamente descritta con un semplice oggetto JavaScript. Niente di più.

C'è solo una cosa da tenere presente. Un'azione deve avere un campo tipo. Questo campo descrive l'intento dell'azione.

Nella storia della banca, se dovessimo descrivere la tua azione alla banca, sarebbe simile al seguente:

{
  tipo: "draw_money "
}

Questo è tutto, davvero.

Un'azione Redux è descritta come un oggetto semplice.

Dai un'occhiata all'azione sopra.

Pensi che solo il campo del tipo descriva accuratamente la tua presunta azione per effettuare un prelievo presso una banca?

Hmmm. Io non la penso così. Che ne dici della quantità di denaro che vuoi prelevare?

Molte volte la tua azione avrà bisogno di alcuni dati extra per una descrizione completa. Considera l'azione di seguito. Sostengo che ciò comporti un'azione più ben descritta.

{
  tipo: "draw_money ",
  importo: "$ 4000"
}

Ora, ci sono informazioni sufficienti che descrivono l'azione. Nell'esempio, ignora ogni altro dettaglio che l'azione può includere, come il numero del tuo conto bancario.

Oltre al campo del tipo, la struttura della tua azione Redux dipende davvero da te.

Tuttavia, un approccio comune è di avere un campo tipo e un campo payload come mostrato di seguito:

{
  genere: " ",
  payload: {}
}

Il campo del tipo descrive l'azione e tutti gli altri dati / informazioni richiesti che descrivono l'azione vengono inseriti nell'oggetto payload.

Per esempio:

{
  tipo: "draw_money ",
  payload: {
     importo: "$ 4000"
  }
}

Quindi si! Questa è un'azione.

Gestione delle risposte alle azioni nel riduttore

Ora che hai capito bene cos'è un'azione, è importante vedere come diventano utili in senso pratico.

In precedenza, ho detto che un riduttore accetta due argomenti. Uno stato, l'altra azione.

Ecco come appare un semplice riduttore:

riduttore di funzione (stato, azione) {
  // ritorna nuovo stato
}

L'azione viene passata come secondo parametro al Riduttore. Ma non abbiamo fatto nulla con esso all'interno della funzione stessa.

Per gestire le azioni passate nel riduttore, in genere scrivi un'istruzione switch all'interno del riduttore, in questo modo:

riduttore di funzione (stato, azione) {
switch (action.type) {
caso "ritiro_money":
//fare qualcosa
rompere;
caso "deposito":
//fare qualcosa
rompere;
predefinito:
stato di ritorno;
}
}

Ad alcune persone sembra non piacere l'istruzione switch, ma è fondamentalmente un if / else per possibili valori su un singolo campo.

Il codice sopra cambierà il tipo di azione e farà qualcosa in base al tipo di azione passata. Tecnicamente, il bit di fare qualcosa è richiesto per restituire un nuovo stato.

Lasciami spiegare ulteriormente.

Supponiamo di avere due ipotetici pulsanti, il pulsante n. 1 e il pulsante n. 2, su una determinata pagina Web e l'oggetto statale assomiglierebbe a questo:

{
isOpen: true,
isClicked: false,
  }

Quando si fa clic sul pulsante n. 1, si desidera attivare o disattivare il campo isOpen. Nel contesto di un'app React, la soluzione è semplice. Non appena si fa clic sul pulsante, si dovrebbe fare questo:

this.setState ({isOpen:! this.state.isOpen})

Inoltre, supponiamo che quando si fa clic su # 2, si desidera aggiornare il campo isClicked. Ancora una volta, la soluzione è semplice e sulla falsariga di questo:

this.setState ({isClicked:! this.state.isClicked})

Buono.

Con un'app Redux, non è possibile utilizzare setState () per aggiornare l'oggetto stato gestito da Redux.

Devi prima inviare un'azione.

Supponiamo che le azioni siano le seguenti:

# 1:

{
tipo: "is_open"
}

# 2:

{
tipo: "is_clicked"
}

In un'app Redux, ogni azione scorre attraverso il riduttore.

Tutti loro. Quindi, in questo esempio, sia l'azione n. 1 che l'azione n. 2 passeranno attraverso lo stesso riduttore.

In questo caso, in che modo il riduttore distingue ciascuno di essi?

Sì, hai indovinato.

Commutando il tipo di azione, possiamo gestire entrambe le azioni senza problemi.

Ecco cosa intendo:

riduttore di funzione (stato, azione) {
switch (action.type) {
case "is_open":
ritorno; // ritorna nuovo stato
case "is_clicked":
ritorno; // ritorna nuovo stato
predefinito:
stato di ritorno;
}
}

Ora vedi perché l'istruzione switch è utile. Tutte le azioni scorreranno attraverso il riduttore. Pertanto, è importante gestire ciascun tipo di azione separatamente.

Nella sezione successiva, continueremo con l'attività di creazione della mini app di seguito:

Esame delle azioni nell'applicazione

Come ho spiegato in precedenza, ogni volta che si intende aggiornare lo stato dell'applicazione, è necessario inviare un'azione.

Se tale intento è iniziato da un clic dell'utente, da un evento di timeout o persino da una richiesta Ajax, la regola rimane la stessa. Devi inviare un'azione.

Lo stesso vale per questa applicazione.

Poiché intendiamo aggiornare lo stato dell'applicazione, ogni volta che si fa clic su uno dei pulsanti, è necessario inviare un'azione.

Innanzitutto, descriviamo le azioni.

Provalo e vedi se lo ottieni.

Ecco cosa mi è venuto in mente:

Per il pulsante Reagisci:

{
    tipo: "SET_TECHNOLOGY",
    testo: "Reagisci"
  }

Per il pulsante React-Redux:

{
     tipo: "SET_TECHNOLOGY",
     testo: "React-redux"
   }

E infine:

{
   tipo: "SET_TECHNOLOGY",
  testo: "olmo"
}

Facile vero?

Si noti che le tre azioni hanno lo stesso tipo di campo. Questo perché i tre pulsanti fanno tutti la stessa cosa. Se fossero clienti in una banca, avrebbero depositato tutti denaro, ma importi diversi. Il tipo di azione sarà quindi DEPOSIT_MONEY ma con campi importo diversi.

Inoltre, noterai che il tipo di azione è tutto scritto in maiuscolo. Era intenzionale. Non è obbligatorio, ma è uno stile piuttosto popolare nella comunità Redux.

Spero che tu ora capisca come mi sono inventato le azioni.

Presentazione di Action Creators

Dai un'occhiata alle azioni che abbiamo creato sopra. Noterai che stiamo ripetendo alcune cose.

Per uno, hanno tutti lo stesso campo di tipo. Se dovessimo spedire queste azioni in più punti, dovremmo duplicarli dappertutto. Non va bene. Soprattutto perché è una buona idea mantenere il codice ASCIUTTO.

Possiamo fare qualcosa al riguardo?

Sicuro!

Benvenuto, creatori di azioni.

Redux ha tutti questi nomi fantasiosi, eh? Riduttori, azioni e ora, creatori di azioni :)

Lasciami spiegare cosa sono quelli.

I creatori di azioni sono semplicemente funzioni che ti aiutano a creare azioni. È tutto. Sono funzioni che restituiscono oggetti azione.

Nel nostro esempio particolare, potremmo creare una funzione che accetterà un parametro di testo e restituirà un'azione, come questa:

export function setTechnology (testo) {
  ritorno {
     tipo: "SET_TECHNOLOGY",
     tecnologia: testo
   }
}

Ora non dobbiamo preoccuparci di duplicare il codice ovunque. Possiamo semplicemente chiamare il creatore dell'azione setTechnology in qualsiasi momento e riceveremo un'azione!

Che buon uso delle funzioni.

Utilizzando ES6, il creatore dell'azione che abbiamo creato sopra potrebbe essere semplificato in questo modo:

const setTechnology = text => ({tipo: "SET_TECHNOLOGY", testo});

Adesso è fatto.

Riunire tutto

Nelle sezioni precedenti abbiamo discusso di tutti i componenti importanti necessari per creare l'app Hello World più avanzata.

Ora mettiamo insieme tutto e costruiamo l'app. Eccitato?

Innanzitutto, parliamo della struttura delle cartelle.

Quando arrivi in ​​una banca, il Cassiere probabilmente si trova nel proprio cubicolo / ufficio. Il Vault è anche tenuto al sicuro in una stanza protetta. Per buoni motivi, le cose sembrano un po 'più organizzate in quel modo. Ognuno nel proprio spazio.

Lo stesso si può dire per Redux.

È pratica comune avere gli attori principali di un'app redux in diretta nella propria cartella / directory.

Per attori, intendo, il riduttore, le azioni e il negozio.

È comune creare tre diverse cartelle all'interno della directory dell'app e nominare ognuna con questi attori.

Questo non è un must - e inevitabilmente, decidi tu come strutturare il tuo progetto. Per le grandi applicazioni, tuttavia, questa è certamente una pratica abbastanza decente.

Rifattorizzeremo ora le directory delle app correnti che abbiamo. Crea alcune nuove directory / cartelle. Uno chiamato riduttori, un altro, negozio e l'ultimo, azioni

Ora dovresti avere una struttura componente che assomiglia a questa:

In ciascuna delle cartelle, creare un file index.js. Questo sarà il punto di ingresso per ciascuno degli attori Redux (riduttori, magazzini e azioni). Li chiamo attori, come attori cinematografici. Sono i componenti principali di un sistema Redux.

Ora rifattorizzeremo l'app precedente dal Capitolo 2: La tua prima applicazione Redux, per utilizzare questa nuova struttura di directory.

negozio / index.js

importare {createStore} da "redux";
riduttore di importazione da "../riduttori";

const initialState = {tech: "React"};
export const store = createStore (riduttore, initialState);

Questo è proprio come prima. L'unica differenza è che il negozio è ora creato nel suo file index.js, come avere cubicoli / uffici separati per i diversi attori Redux.

Ora, se abbiamo bisogno del negozio ovunque all'interno della nostra app, possiamo importare in sicurezza il negozio, come nel negozio di importazione da "./store";

Detto questo, il file App.js per questo esempio particolare è leggermente diverso dal primo.

App.js

import React, {Component} da "reagire";
importare HelloWorld da "./HelloWorld";
importa ButtonGroup da "./ButtonGroup";
importare {store} da "./store";

class App estende Component {
  render () {
    ritorno [
      ,
      
    ];
  }
}

esportazione app predefinita;

Ciò che è diverso?

Nella riga 4, il negozio viene importato dal proprio "cubicolo". Inoltre, ora esiste un componente che accetta una serie di tecnologie e sputa i pulsanti. Il componente ButtonGroup gestisce il rendering dei tre pulsanti sotto il testo "Hello World".

Inoltre, potresti notare che il componente App restituisce un array. Questo è un regalo di React 16. Con React 16, non devi avvolgere elementi JSX adiacenti in un div. Se lo desideri, puoi utilizzare un array, ma passa un oggetto chiave a ciascun elemento dell'array.

Questo è tutto per il componente App.js.

L'implementazione del componente ButtonGroup è abbastanza semplice. Ecco qui:

ButtonGroup.js

import React da "reagire";

const ButtonGroup = ({technologies}) => (
  
    {technologies.map ((tech, i) => (                {} Tech            ))}    ); esportare ButtonGroup predefinito;

ButtonGroup è un componente senza stato che include una serie di tecnologie, indicate da tecnologie.

Passa sopra questo array usando map e rende un pulsante

In questo momento, tutto viene visualizzato correttamente, ma facendo clic sul pulsante, non succede ancora nulla.

Bene, questo perché non abbiamo ancora fornito gestori di clic. Facciamolo adesso.

All'interno della funzione di rendering, impostiamo un gestore onClick:

    {technologies.map ((tech, i) => (                {} Tech            ))}   

Buono. Scriviamo ora il dispatchBtnAction.

Non dimenticare che l'unico obiettivo di questo gestore è inviare un'azione quando si verifica un clic.

Ad esempio, se fai clic sul pulsante Reagisci, invia l'azione:

{
    tipo: "SET_TECHNOLOGY",
    tecnologia: "Reagisci"
  }

Se fai clic sul pulsante React-Redux, invia questa azione:

{
     tipo: "SET_TECHNOLOGY",
     tecnologia: "React-redux"
   }

Quindi, ecco la funzione dispatchBtnAction.

function dispatchBtnAction (e) {
  const tech = e.target.dataset.tech;
  store.dispatch (setTechnology (Tech));
}

Hmmm. Il codice sopra ha senso per te?

e.target.dataset.tech otterrà l'attributo dei dati impostato sul pulsante, data-tech. Quindi, la tecnologia manterrà il valore del testo.

store.dispatch () è il modo in cui invii un'azione in Redux e setTechnology () è il creatore di azioni che abbiamo scritto in precedenza!

function setTechnology (testo) {
  ritorno {
     tipo: "SET_TECHNOLOGY",
     testo: testo
   }
}

Sono andato avanti e ho aggiunto alcuni commenti nell'illustrazione seguente, solo per capire il codice.

Come già sapete, store.dispatch si aspetta un oggetto azione e nient'altro. Non dimenticare il creatore dell'azione setTechnology. Accetta il testo del pulsante e restituisce l'azione richiesta.

Inoltre, la tecnologia del pulsante viene acquisita dal set di dati del pulsante. Vedi, è esattamente per questo che avevo un attributo di tecnologia dei dati su ciascun pulsante. Quindi potremmo facilmente togliere la tecnologia da ciascuno dei pulsanti.

Ora stiamo inviando le azioni giuste. Possiamo dire se ora funziona come previsto?

Azioni inviate. Questa cosa funziona?

Innanzitutto, ecco una breve domanda a quiz. Facendo clic su un pulsante e di conseguenza inviando un'azione, cosa succede dopo in Redux? Quale degli attori Redux entra in gioco?

Semplice. Quando colpisci la banca con un'azione WITHRAW_MONEY, a chi vai? Il cassiere, sì.

Stessa cosa qui. Le azioni, una volta inviate, scorrono attraverso il riduttore.

Per dimostrarlo, registrerò qualsiasi azione venga inserita nel riduttore.

riduttori / index.js

export default (state, action) => {
  console.log (azione);
  stato di ritorno;
};

Il riduttore restituisce quindi il nuovo stato dell'app. Nel nostro caso particolare, stiamo solo restituendo lo stesso stato iniziale.

Con console.log () nel riduttore, diamo un'occhiata a cosa succede quando facciamo clic.

O si!

Le azioni vengono registrate quando si fa clic sui pulsanti. Il che dimostra che le azioni passano davvero attraverso il Riduttore. Sorprendente!

C'è ancora un'altra cosa. Non appena viene avviata l'app, viene registrata anche una strana azione. Sembra così:

{tipo: "@@ redux / INITu.r.5.b.c"}

Cos'è quello?

Bene, non ti preoccupare così tanto. È un'azione passata dallo stesso Redux durante la configurazione della tua app. Di solito viene chiamata azione init Redux e viene passata nel riduttore quando Redux inizializza l'applicazione con lo stato iniziale dell'app.

Ora, siamo sicuri che le azioni passano davvero attraverso il Riduttore. Grande!

Sebbene sia eccitante, l'unica ragione per cui vai alla Cassa con una richiesta di prelievo è perché vuoi soldi. Se il Riduttore non sta eseguendo l'azione in cui passiamo e sta facendo qualcosa con la nostra azione, di che valore ha?

Far contare il riduttore

Fino ad ora, il riduttore su cui abbiamo lavorato non ha fatto nulla di particolarmente intelligente. È come un cassiere che è nuovo nel lavoro e non fa nulla con il nostro intento WITHDRAW_MONEY.

Cosa ci aspettiamo esattamente dal riduttore?

Per ora, ecco lo stato iniziale che abbiamo passato a createStore al momento della creazione di STORE.

const initialState = {tech: "React"};
export const store = createStore (riduttore, initialState);

Quando un utente fa clic su uno qualsiasi dei pulsanti, passando così un'azione al riduttore, il nuovo stato che ci aspettiamo che il riduttore ritorni dovrebbe avere il testo dell'azione lì dentro!

Ecco cosa intendo.

Lo stato corrente è {tech: "React"}

Data una nuova azione di tipo SET_TECHNOLOGY e testo, React-Redux:

{
tipo: "SET_TECHNOLOGY",
testo: "React-Redux"
}

Cosa ti aspetti che sia il nuovo stato?

Sì, {tech: "React-Redux"}

L'unico motivo per cui abbiamo inviato un'azione è perché vogliamo un nuovo stato dell'applicazione!

Come ho accennato in precedenza, il modo comune di gestire diversi tipi di azioni all'interno di un riduttore è utilizzare l'istruzione switch JavaScript come mostrato di seguito:

export default (state, action) => {
  switch (action.type) {
    caso "SET_TECHNOLOGY":
      //fare qualcosa.

    predefinito:
      stato di ritorno;
  }
};

Ora passiamo al tipo di azione. Ma perché?

Bene, se andassi a vedere un Cassiere, potresti avere in mente molte azioni diverse.

Potresti voler WITHDRAW_MONEY o DEPOSIT_MONEY o forse solo SAY_HELLO.

Il cassiere è intelligente, quindi prendono parte alle tue azioni e rispondono in base alle tue intenzioni.

Questo è esattamente ciò che stiamo facendo con il riduttore.

L'istruzione switch verifica il tipo di azione.

Che cosa vuoi fare? Prelevare, depositare, qualunque cosa ...

Successivamente, gestiamo i casi noti che ci aspettiamo. Per ora, c'è solo un caso che è SET_TECHNOLOGY.

E per impostazione predefinita, assicurati di restituire semplicemente lo stato dell'app.

Fin qui tutto bene.

Il cassiere (riduttore) ora comprende la nostra azione. Tuttavia, non ci stanno ancora dando soldi (stato).

Facciamo qualcosa all'interno del caso.

Ecco la versione aggiornata del riduttore. Uno che in realtà ci dà soldi :)

export default (state, action) => {
  switch (action.type) {
    caso "SET_TECHNOLOGY":
      ritorno {
        ...stato,
        tech: action.text
      };

    predefinito:
      stato di ritorno;
  }
};

Oh sì!

Vedi cosa ci faccio lì?

Spiegherò cosa sta succedendo nella prossima sezione.

Mai mutare lo stato all'interno dei riduttori

Quando si restituisce lo stato dai riduttori, c'è qualcosa che potrebbe rimandarti all'inizio. Tuttavia, se hai già scritto un buon codice React, dovresti avere familiarità con questo.

Non devi mutare lo stato ricevuto nel tuo Riduttore. Invece, dovresti sempre restituire una nuova copia dello stato.

Tecnicamente, non dovresti mai farlo:

export default (state, action) => {
  switch (action.type) {
    caso "SET_TECHNOLOGY":
      state.tech = action.text;
      stato di ritorno;

    predefinito:
      stato di ritorno;
  }
};

Questo è esattamente il motivo per cui il riduttore che ho scritto ha restituito questo:

ritorno {
        ...stato,
        tech: action.text
  };

Invece di mutare (o cambiare) lo stato ricevuto dal riduttore, sto restituendo un nuovo oggetto. Questo oggetto ha tutte le proprietà dell'oggetto stato precedente. Grazie all'operatore spread ES6, ... state. Tuttavia, il campo tecnologico viene aggiornato a ciò che viene dall'azione, action.text.

Inoltre, ogni Reducer che scrivi dovrebbe essere una funzione pura senza effetti collaterali: nessuna chiamata API o aggiornamento di un valore al di fuori dell'ambito della funzione.

Capito?

Speriamo di si.

Ora, il Cassiere non sta ignorando le nostre azioni. In effetti ci stanno dando soldi adesso!

Dopo aver fatto ciò, fai clic sui pulsanti. Funziona adesso?

Accidenti ancora non funziona. Il testo non si aggiorna.

Cosa c'è di sbagliato nel mondo questa volta?

Iscrizione agli aggiornamenti dello store

Quando visiti la banca, fai sapere al Cassiere la tua azione di RECESSO prevista e ricevi con successo i tuoi soldi - quindi qual è il prossimo?

Molto probabilmente, riceverai un avviso via e-mail / testo o qualche altra notifica mobile che dice che hai eseguito una transazione e che il saldo del tuo nuovo account è così e così.

Se non ricevi notifiche per dispositivi mobili, riceverai sicuramente una sorta di "ricevuta personale" per dimostrare che sul tuo account è stata effettuata una transazione riuscita.

Va bene, nota il flusso. È stata avviata un'azione, hai ricevuto i tuoi soldi, hai ricevuto un avviso per una transazione riuscita.

Sembra che si sia verificato un problema con il nostro codice Redux.

È stata avviata un'azione correttamente, abbiamo ricevuto denaro (stato), ma ehi, dov'è l'avviso per un aggiornamento dello stato riuscito?

Non ne abbiamo nessuno.

Bene, c'è una soluzione. Da dove vengo, ti iscrivi per ricevere notifiche di transazione dalla banca tramite e-mail / SMS.

Lo stesso vale per Redux. Se vuoi gli aggiornamenti, devi abbonarti.

Ma come?

Il negozio Redux, qualunque negozio tu crei ha un metodo di sottoscrizione chiamato così: store.subscribe ().

Una funzione ben chiamata, se me lo chiedi!

L'argomento passato in store.subscribe () è una funzione e verrà invocato ogni volta che c'è un aggiornamento di stato.

Per quello che vale, ricorda che l'argomento passato in store.subscribe () dovrebbe essere una funzione. Va bene?

Ora approfittiamo di questo.

Pensaci. Dopo l'aggiornamento dello stato, cosa vogliamo o ci aspettiamo? Ci aspettiamo un nuovo rendering, giusto?

Quindi, lo stato è stato aggiornato. Redux, per favore, esegui nuovamente il rendering dell'app con i nuovi valori di stato.

Diamo un'occhiata a dove viene visualizzata l'app in index.js

Ecco cosa abbiamo.

ReactDOM.render (, document.getElementById ("root")

Questa è la linea che esegue il rendering dell'intera applicazione. Prende il componente App /> e lo rende nel DOM. L'ID root deve essere specifico.

Innanzitutto, riassumiamolo in una funzione.

Guarda questo:

const render = function () {
  ReactDOM.render (, document.getElementById ("root")
}

Poiché ora è all'interno di una funzione, dobbiamo richiamare la funzione per eseguire il rendering dell'app.

const render = function () {
   ReactDOM.render (, document.getElementById ("root")
}
render ()

Ora, l ' verrà visualizzata come prima.

Utilizzando alcuni gadget ES6, la funzione può essere semplificata.

const render = () => ReactDOM.render (, document.getElementById ("root"));

render ();

Avere il rendering dell ' racchiuso in una funzione significa che ora possiamo iscriverci agli aggiornamenti del negozio in questo modo:

store.subscribe (rendering);

Dove render è l'intera logica di rendering per - quella appena rifattorizzata.

Capisci cosa sta succedendo qui, giusto?

Ogni volta che viene eseguito correttamente l'aggiornamento allo store, verrà ora ridistribuito con i nuovi valori di stato.

Per chiarezza, ecco il componente :

class App estende Component {
  render () {
    ritorno [
      ,
      
    ];
  }
}

Ogni volta che si verifica un nuovo rendering, store.getState () alla riga 4 ora recupera lo stato aggiornato.

Vediamo se l'app ora funziona come previsto.

Si! Funziona e sapevo che potevamo farlo!

Stiamo inviando con successo un'azione, ricevendo denaro dalla Cassa e quindi iscrivendoci per ricevere notifiche. Perfetto!

Nota importante sull'uso di store.subscribe ()

Ci sono alcuni avvertimenti sull'uso di store.subscribe () come abbiamo fatto qui. È un'API Redux di basso livello.

In produzione, e in gran parte per motivi di prestazioni, è probabile che tu utilizzi i binding come reattivo-redux quando hai a che fare con app più grandi. Per ora, è sicuro continuare a utilizzare store.subscribe () per i nostri scopi di apprendimento.

In uno dei più bei commenti di PR che vedo da molto tempo, Dan Abramov, in uno degli esempi di applicazione di Redux, ha dichiarato:

Il nuovo esempio Counter Vanilla ha lo scopo di dissipare il mito secondo cui Redux richiede Webpack, React, hot ricaricamento, saghe, creatori di azioni, costanti, Babele, npm, moduli CSS, decoratori, latino fluente, un abbonamento Egghead, un dottorato o un Exceeds Aspettative GUFO livello.

Credo lo stesso.

Quando impari Redux, soprattutto se hai appena iniziato, puoi eliminare il maggior numero possibile di "extra".

Impara a camminare per primo, poi puoi correre quanto vuoi.

Ok, siamo ancora fatti?

Sì, abbiamo finito, tecnicamente. Tuttavia, c'è un'altra cosa che mi piacerebbe mostrarti. Visualizzo il mio browser Devtools e abiliterò il flashing.

Ora, quando facciamo clic e aggiorniamo lo stato dell'app, notate i lampi verdi che appaiono sullo schermo. I lampeggi verdi rappresentano parti dell'app che vengono ridipinte o ridistribuite dal motore del browser.

Dare un'occhiata:

Come puoi vedere, anche se sembra che la funzione di rendering sia invocata ogni volta che viene effettuato un aggiornamento dello stato, non viene eseguita nuovamente il rendering dell'intera app. Viene ridistribuito solo il componente con un nuovo valore di stato. In questo caso, il componente .

Un'altra cosa.

Se viene eseguito il rendering dello stato corrente dell'app, Hello World React, facendo nuovamente clic sul pulsante Reagisci non viene eseguito nuovamente il rendering poiché il valore dello stato è lo stesso.

Buono!

Questo è l'algoritmo React Virtual DOM Diff al lavoro qui. Se conosci qualche React, devi averlo già sentito prima.

Quindi si Abbiamo finito con questa sezione! Mi sto divertendo così tanto a spiegarlo. Spero che ti piaccia anche la lettura.

Conclusione e sintesi

Per un'applicazione apparentemente semplice, questo capitolo è stato più lungo del previsto. Ma va bene. Ora sei dotato di una conoscenza ancora maggiore su come funziona Redux.

Ecco alcune cose che hai imparato in questo capitolo:

  • A differenza di setState () in React puro, l'unico modo per aggiornare lo stato di un'applicazione Redux è inviare un'azione.
  • Un'azione è descritta accuratamente con un semplice oggetto JavaScript, ma deve avere un campo di tipo.
  • In un'app Redux, ogni azione scorre attraverso il riduttore. Tutti loro.
  • Usando un'istruzione switch, è possibile gestire diversi tipi di azioni all'interno di Reducer.
  • I creatori di azioni sono semplicemente funzioni che restituiscono oggetti di azione.
  • È pratica comune avere gli attori principali di un'app redux in diretta nella propria cartella / directory.
  • Non devi mutare lo stato ricevuto nel tuo Riduttore. Invece, dovresti sempre restituire una nuova copia dello stato.
  • Per iscriversi agli aggiornamenti del negozio, utilizzare il metodo store.subscribe ().

esercizi

Bene, ora è il momento di fare qualcosa di bello.

  1. Nei file degli esercizi, ho impostato una semplice applicazione React che modella l'applicazione bancaria di un utente.

Dai un'occhiata al modello sopra. Oltre a poter visualizzare il saldo totale, l'utente può anche eseguire azioni di prelievo.

Il nome e il saldo dell'utente sono memorizzati nello stato dell'applicazione.

{
  nome: "Ohans Emmanuel",
  saldo: 1559.30
}

Ci sono due cose che devi fare.

(i) Rifattorizzare lo stato dell'App come gestito esclusivamente da Redux.

(ii) Gestire le azioni di prelievo per esaurire effettivamente il saldo dell'utente (ovvero facendo clic sui pulsanti, il saldo si riduce).

Devi farlo solo tramite Redux.

Come promemoria, dopo aver scaricato l'Ebook, troverai le istruzioni su come ottenere i file di codice, i file di allenamento e le soluzioni di allenamento associati.

2. L'immagine seguente è quella di un contatore del tempo creato come applicazione React.

L'oggetto stato si presenta così:

{
  giorni: 11,
  ore: 31,
  minuti: 27,
  secondi: 11,
  activeSession: "minuti"
}

A seconda della sessione attiva, facendo clic su uno dei pulsanti “aumenta” o “diminuisci” si dovrebbe aggiornare il valore visualizzato nel contatore.

Ci sono due cose che devi fare.

(i) Rifattorizzare lo stato dell'App come gestito esclusivamente da Redux.

(ii) Gestire le azioni di aumento e diminuzione per influenzare effettivamente il tempo visualizzato sul contatore.

Capitolo 4: Building Skypey: un esempio più avanzato.

Abbiamo fatto molta strada e ti saluto per aver seguito.

In questa sezione, ti guiderò attraverso il processo di costruzione di un esempio più avanzato.

Anche se abbiamo coperto molto terreno sulle basi di Redux, penso davvero che questo esempio ti darà una prospettiva più profonda di come alcuni dei concetti che hai imparato funzionano su una scala molto più ampia.

Parleremo di pianificazione dell'applicazione, progettazione e normalizzazione dell'oggetto stato e molto altro ancora. Le app reali richiedono molto di più di Redux. Avrai comunque bisogno di alcuni CSS e anche React.

Allacciate le cinture, poiché questa sarà una lunga corsa degna!

Pianificazione dell'applicazione

Va bene. Ecco la grande domanda. Cosa fai generalmente prima quando avvii una nuova applicazione React?

Bene, abbiamo tutti le nostre preferenze.

Suddividi l'intera applicazione in componenti e ti fai strada?

Cominci prima con il layout generale dell'applicazione?

Che ne dici dell'oggetto stato della tua app? Passi qualche volta a pensare anche a quello?

C'è davvero molto da considerare. Ti lascio con il tuo modo preferito di fare le cose.

Nel costruire Skypey, seguirò un approccio dall'alto verso il basso. Discuteremo il layout generale dell'app, quindi il design dell'oggetto stato dell'app, quindi costruiremo i componenti più piccoli.

Ancora una volta, non esiste un modo perfetto per farlo. Per un progetto più complesso, forse, un approccio dal basso verso l'alto sarebbe adatto a questo.

Ancora una volta, ecco il risultato finale che stiamo cercando di ottenere:

L'applicazione Skypey

Risoluzione del layout iniziale dell'app

Dalla CLI, crea una nuova app di reazione con create -eagire-app e chiamala Skypey.

creare-reagire-app Skypey

Il layout di Skypey è un semplice layout a 2 colonne. Una barra laterale a larghezza fissa a sinistra e a destra una sezione principale che occupa la larghezza restante della finestra.

Ecco una breve nota su come viene disegnata questa app.

Se sei un ingegnere più esperto, assicurati di utilizzare qualsiasi CSS nella soluzione JavaScript adatta a te. Per semplicità, modellerò l'app Skypey con un buon vecchio CSS, niente di più.

Facciamo crack.

Crea due nuovi file, Sidebar.js e Main.js nella directory principale.

Come avrai intuito, quando creeremo la barra laterale e i componenti principali, avremo il rendering all'interno del componente dell'app in questo modo:

App.js

const App = () => {
  ritorno (
    
                     ); };

Suppongo che tu abbia familiarità con la struttura di un progetto di creazione-reazione-app. C'è il punto di ingresso dell'app, index.js che esegue il rendering di un componente dell'app.

Prima di passare alla costruzione della barra laterale e dei componenti principali, prima di tutto un po 'di pulizie CSS. Assicurati che il nodo DOM in cui viene eseguito il rendering dell'app, #root, occupi l'intera altezza della finestra.

index.css

#root {
  altezza: 100 vh;
}

Mentre ci sei, dovresti anche rimuovere qualsiasi spaziatura indesiderata dal corpo:

body {
  margine: 0;
  imbottitura: 0;
  famiglia di caratteri: sans-serif;
}

Buono!

Il layout dell'app sarà strutturato usando Flexbox.

Fai funzionare il succo Flexbox creando .App un contenitore flessibile e assicurandoti che occupi il 100% dell'altezza disponibile.

App.css

.App {
  altezza: 100%;
  display: flessibile;
  colore: rgba (189, 189, 192, 1);
}

Ora possiamo comodamente realizzare la sidebar e i componenti principali.

Manteniamolo semplice per ora.

Sidebar.js

import React da "reagire";
import "./Sidebar.css";

const Sidebar = () => {
  return  Sidebar ;
};

esportazione barra laterale predefinita;

Tutto ciò che viene visualizzato è la barra laterale di testo all'interno di un elemento . Inoltre, tieni presente che è stato importato anche un foglio di stile corrispondente, Sidebar.css.

All'interno di Sidebar.css dobbiamo limitare la larghezza della barra laterale, oltre ad alcuni altri stili semplici.

Sidebar.css

.Sidebar {
  larghezza: 80px;
  colore di sfondo: rgba (32, 32, 35, 1);
  altezza: 100%;
  bordo-destra: 1px solido rgba (189, 189, 192, 0,1);
  transizione: larghezza 0,3 s;
}

/ * non piccoli dispositivi * /
@media (larghezza minima: 576px) {
  .Sidebar {
    larghezza: 320px;
  }
}

Adottando un approccio mobile-first, la larghezza della barra laterale sarà 80px e 320px su dispositivi più grandi.

Bene, ora passiamo al componente principale.

Come prima, lo terremo semplice.

Rendi semplicemente un semplice testo all'interno di un elemento

.

Durante lo sviluppo di app, vuoi essere sicuro di costruire progressivamente. In altre parole, crea i bit e assicurati che l'app funzioni.

Di seguito il componente

:

import React da "reagire";
import "./Main.css";

const Main = () => {
  return 
Main Stuff
; }; esportazione predefinita Main;

Ancora una volta, è stato importato un foglio di stile corrispondente, Main.css.

Con gli elementi renderizzati di

e , esistono i nomi delle classi CSS, .Main e .Sidebar.

Poiché i componenti sono entrambi renderizzati in , le classi .Sidebar e .Main sono figli della classe genitrice, .App.

Ricorda che .App è un contenitore flessibile. Di conseguenza, .Main può essere creato per riempire lo spazio rimanente nella finestra in questo modo:

.Principale {
 flex: 1 1 0;
}

Ora, ecco il codice completo:

.Principale {
  flex: 1 1 0;
  colore di sfondo: rgba (25, 25, 27, 1);
  altezza: 100%;
}

È stato facile :)

Ed ecco il risultato di tutto il codice che abbiamo scritto fino a questo punto.

L'umile risultato con la barra laterale e le sezioni principali disposte.

Non così eccitante. Pazienza. Ci arriveremo

Per ora, è impostato il layout di base dell'applicazione. Molto bene!

Progettazione dell'oggetto State

Il modo in cui vengono create le app React è che l'intera app è principalmente una funzione dell'oggetto stato.

Che tu stia creando un'applicazione sofisticata o qualcosa di semplice, è necessario riflettere molto su come strutturerai l'oggetto stato della tua app.

Soprattutto quando si lavora con Redux, è possibile ridurre molta complessità progettando l'oggetto stato correttamente.

Quindi, come lo fai nel modo giusto?

Innanzitutto, considera l'app Skypey.

Un utente dell'app ha più contatti.

I contatti multipli che un utente può avere.

Ogni contatto a sua volta ha un numero di messaggi, che compongono la loro conversazione con l'utente dell'app principale. Questa vista viene attivata quando si fa clic su uno dei contatti.

Facendo clic su un contatto viene visualizzato il loro messaggio nel riquadro principale.

Per associazione, non avresti torto di avere una foto come questa nella tua mente.

Hmmm… .Una casella utente gigante con contatti nidificati

Puoi quindi continuare a descrivere lo stato dell'app in questo modo.

Un array di utenti giganti con contatti nidificati

Ok, in semplice JavaScript, ecco cosa probabilmente avresti:

const state = {
  utente: [
    {
      contact1: 'Alex',
      messaggi: [
        'MSG1',
        'MSG2',
        'Msg3'
      ]
    },
    {
      contact2: 'john',
      messaggi: [
        'MSG1',
        'MSG2',
        'Msg3'
      ]
    }
  ]

All'interno dell'oggetto stato sopra è presente un campo utente rappresentato da un array gigante. Poiché l'utente ha un numero di contatti, questi sono rappresentati da oggetti all'interno dell'array. Oh, poiché potrebbero esserci molti messaggi diversi, anche questi sono memorizzati in un array.

A prima vista, questa può sembrare una soluzione decente.

Ma è?

Se dovessi ricevere dati da qualche back-end, la struttura potrebbe apparire così!

Bene vero?

No amico. Non così buono.

Questa è una rappresentazione abbastanza buona dei dati. Sembra che mostri la relazione tra ciascuna entità, ma in termini di stato dell'applicazione front-end, questa è una cattiva idea. Bad è una parola forte. Diciamo solo che esiste un modo migliore per farlo.

Ecco come la vedo io.

Se dovessi gestire una squadra di calcio, un buon piano sarebbe quello di scegliere i migliori marcatori della squadra e metterli in primo piano per raggiungere i tuoi obiettivi.

Puoi sostenere che i buoni giocatori possono segnare da qualsiasi luogo - sì. Scommetto che saranno più efficaci quando saranno ben posizionati davanti al palo dell'opposizione.

Lo stesso vale per l'oggetto stato.

Scegli i corridori frontali all'interno dell'oggetto stato e posizionali in "fronte".

Quando dico "front runner", intendo i campi dell'oggetto stato su cui eseguirai più azioni CRUD. Le parti dello stato che creerai, leggerai, aggiornerai e cancellerai più spesso di altre. Le parti dello stato che sono fondamentali per l'applicazione.

Questa non è una regola di ferro, ma è una buona metrica da seguire.

Osservando l'oggetto di stato corrente e le esigenze della nostra applicazione, possiamo scegliere insieme i "corridori frontali".

Per uno, leggeremo il campo "Messaggi" abbastanza spesso, per ogni contatto dell'utente. C'è anche la necessità di modificare ed eliminare il messaggio di un utente.

Ora, quello è un front runner proprio lì.

Lo stesso vale anche per "Contatti".

Ora mettiamoli "di fronte".

Ecco come.

Invece di avere i campi "Messaggi" e "Contatti" nidificati, selezionarli e renderli chiavi primarie all'interno dell'oggetto stato. Come questo:

const state = {
    utente: [],
    messaggi: [
      'MSG1',
      'MSG2'
    ],
    contatti: ['Contact1', 'Contact2']
  }

Questa è ancora una rappresentazione incompleta, ma abbiamo notevolmente migliorato la rappresentazione dell'oggetto stato dell'app.

Ora continuiamo.

Ricorda che un utente può inviare messaggi ai propri contatti. Al momento, i messaggi e il campo di contatto all'interno dell'oggetto stato sono indipendenti.

Dopo aver reso questi campi chiavi primarie all'interno dell'oggetto state, non c'è nulla che mostri la relazione tra un determinato messaggio e il contatto associato. Sono indipendenti e non va bene perché dobbiamo sapere quale elenco di messaggi appartiene a chi. Senza saperlo, come possiamo rendere i messaggi corretti quando si fa clic su un contatto?

Non c'è modo. Non possiamo.

Ecco un modo per gestirlo:

const state = {
    utente: [],
    messaggi: [
      {
        messaggio A: 'contact1',
        testo: "Ciao"
      },
      {
        messaggio A: 'contact2',
        testo: "Ehi!"
      }
    ],
    contatti: ['Contact1', 'Contact2']
  }

Quindi, tutto ciò che ho fatto è rendere il campo dei messaggi una matrice di oggetti messaggio. oggetti con un messaggio To key. Questa chiave mostra a quale contatto appartiene un determinato messaggio.

Ci stiamo avvicinando. Solo un po 'di refactoring, e abbiamo finito.

Invece di un semplice array, un utente può essere meglio descritto da un oggetto: un oggetto utente.

utente:  {
    nome,
    e-mail,
    foto del profilo,
    stato:,
    ID utente
  }

Un utente avrà un nome, un'e-mail, una foto del profilo, uno stato di testo elaborato e un ID utente univoco. L'ID utente è importante e deve essere univoco per ciascun utente.

Una visualizzazione visiva di alcune proprietà dell'utente.

Pensaci. I contatti di una persona possono anche essere rappresentati da un oggetto utente simile.

Pertanto, il campo dei contatti all'interno dell'oggetto stato può essere rappresentato da un elenco di oggetti utente.

contatti: [
  {
    nome,
    e-mail,
    foto del profilo,
    stato,
    ID utente
  },
  {
    nome,
    e-mail,
    foto del profilo,
    stato,
    user_id_2
  }
]

Va bene. Fin qui tutto bene.

Il campo dei contatti è ora rappresentato da una vasta gamma di oggetti utente.

Tuttavia, invece di utilizzare un array, possiamo avere anche i contatti rappresentati da un oggetto. Ecco cosa intendo.

Invece di racchiudere tutti i contatti dell'utente in un array gigante, potrebbero anche essere inseriti in un oggetto.

Vedi sotto:

contatti: {
  ID utente: {
    nome,
    e-mail,
    foto del profilo,
    stato,
    ID utente
  },
  user_id_2: {
    nome,
    e-mail,
    foto del profilo,
    stato,
    user_id_2
  }
}

Poiché gli oggetti devono avere una coppia di valori chiave, gli ID univoci dei contatti vengono utilizzati come chiavi per i rispettivi oggetti utente.

Ha senso?

Ci sono alcuni vantaggi nell'uso di oggetti su array. Ci sono anche aspetti negativi.

In questa applicazione, utilizzerò principalmente oggetti per descrivere i campi all'interno dell'oggetto stato.

Se non sei abituato a questo approccio, questo adorabile video spiega alcuni dei vantaggi.

Come ho detto prima, ci sono alcuni svantaggi di questo approccio, ma ti mostrerò come superarli.

Abbiamo risolto il modo in cui il campo dei contatti verrà progettato all'interno dell'oggetto stato dell'applicazione. Ora spostiamoci nel campo dei messaggi.

Al momento abbiamo i messaggi come un array con oggetti messaggio.

messaggi: [
      {
        messaggio A: 'contact1',
        testo: "Ciao"
      },
      {
        messaggio A: 'contact2',
        testo: "Ehi!"
      }
    ]

Definiremo ora una forma più appropriata per gli oggetti messaggio. Un oggetto messaggio sarà rappresentato dall'oggetto messaggio seguente:

{
    testo,
    is_user_msg
};
Nota come alcuni messaggi sono posizionati a sinistra e altri a destra.

Il testo è il testo visualizzato all'interno del fumetto della chat. Tuttavia, is_user_msg sarà un valore booleano: vero o falso. Questo è importante per differenziare se un messaggio proviene da un contatto o dall'utente predefinito dell'app.

Guardando la grafica sopra, noterai che i messaggi dell'utente e quelli di un contatto hanno uno stile diverso nella finestra della chat. I messaggi dell'utente rimangono a destra e il contatto a sinistra. Uno è blu, l'altro è scuro.

Ora vedi perché il valore booleano, is_user_msg è importante. Ne abbiamo bisogno per rendere i messaggi in modo appropriato.

Ad esempio, l'oggetto messaggio potrebbe essere simile al seguente:

{
  testo: "Ciao a tutti. Sei bravo?",
  is_user_msg: false
}

Ora, rappresentando il campo messaggi all'interno dello stato con un oggetto, dovremmo avere qualcosa del genere:

messaggi: {
    ID utente: {
       testo,
       is_user_msg
    },
    user_id_2: {
     testo,
     is_user_msg
   }
 }

Nota come sto usando di nuovo anche un oggetto anziché un array. Inoltre, mapperemo ogni messaggio sulla chiave univoca, user_id del contatto.

Questo perché un utente può avere conversazioni diverse con contatti diversi ed è importante mostrare questa rappresentazione all'interno dell'oggetto stato. Ad esempio, quando si fa clic su un contatto, è necessario sapere quale è stato selezionato!

Come facciamo questo? Sì, con il loro user_id.

La rappresentazione sopra è incompleta ma abbiamo fatto molti progressi! Il campo dei messaggi che abbiamo rappresentato qui presuppone che ogni contatto (rappresentato dal loro ID utente unico) abbia un solo messaggio.

Ma non è sempre così. Un utente può avere molti messaggi inviati avanti e indietro all'interno di una conversazione.

Quindi come possiamo farlo?

Il modo più semplice è avere una matrice di messaggi, ma invece lo rappresenterò con oggetti:

messaggi: {
  ID utente: {
     0: {
        testo,
        is_user_msg
     },
     1: {
       testo,
       is_user_msg
     }
  },
  user_id_2: {
    0: {
       testo,
       is_user_msg
    }
 }
}

Ora stiamo prendendo in considerazione la quantità di messaggi inviati durante una conversazione. Un messaggio, due o più messaggi, sono ora rappresentati nella rappresentazione dei messaggi sopra.

Forse ti starai chiedendo perché ho usato numeri, 0, 1 e così via per creare una mappatura per ciascun messaggio di contatto.

Lo spiegherò dopo.

Per quello che vale, il processo di rimozione di entità nidificate dal tuo oggetto di stato e progettazione come abbiamo fatto qui è chiamato "Normalizzare l'oggetto di stato". Non ti voglio confuso nel caso in cui lo vedi da qualche altra parte.

Il problema principale con l'utilizzo di oggetti su array

Adoro l'idea di utilizzare oggetti su array, per la maggior parte dei casi d'uso. Tuttavia, ci sono alcuni avvertimenti di cui tenere conto.

Avvertenza n. 1: è molto più facile scorrere le matrici nella logica della vista

Una situazione comune in cui ti troverai è la necessità di visualizzare un elenco di componenti.

Ad esempio, per eseguire il rendering di un elenco di utenti a cui viene fornito un prop di utenti, la tua logica sarebbe simile a questa:

const users = this.props.users;

users.map (user => {
ritorna 
})

Tuttavia, se gli utenti sono stati archiviati nello stato come oggetto, quando vengono recuperati e trasmessi come oggetti di scena, gli utenti rimarranno un oggetto. Non puoi usare la mappa sugli oggetti - ed è molto più difficile scorrere su di essi.

Quindi, come possiamo risolverlo?

Soluzione n. 1a:

Usa Lodash per iterare sugli oggetti.

Per chi non lo sapesse, Lodash è una solida libreria di utilità JavaScript. Anche per iterare su array, molti sostengono che usi comunque Lodash in quanto aiuta a gestire i valori di false.

La sintassi per l'uso di Lodash per iterare sugli oggetti non è difficile da capire. Sembra così:

// importa la libreria
importare da "lodash"

// usalo
_.map (utenti, (utente) => {
ritorna 
})

Si chiama il metodo map sull'oggetto Lodash, _.map (). Si passa l'oggetto da ripetere, quindi si passa a una funzione di richiamata come si farebbe con la funzione di mappa JavaScript predefinita.

Soluzione n. 1b:

Considera il solito modo in cui mapperesti su un array per creare un elenco renderizzato di utenti:

const users = this.props.users;

users.map (user => {
ritorna 
})

Ora supponiamo che gli utenti fossero un oggetto. Questo significa che non possiamo mapparci sopra. E se potessimo facilmente convertire gli utenti in un array senza troppi problemi?

Lodash di nuovo in soccorso.

Ecco come sarebbe:

const users = this.props.users; // questo è un oggetto.

_.values ​​(utenti) .map (utente => {
ritorna 
})

Lo vedi?

_.values ​​() convertirà l'oggetto in un array. Questo rende possibile la mappa!

Ecco come funziona.

Se avessi un oggetto utente come questo:

{
 user_id_1: {user_1_object},
 user_id_2 {user_2_object},
 user_id_3: {user_3_object},
 user_id_4: {user_4_object},
}

_.values ​​(utenti) lo convertirà in questo:

[
 {} User_1_object,
 {} User_2_object,
 {} User_3_object,
 {} User_4_object,
]

Sì! Un array con i valori degli oggetti. Esattamente quello che devi ripetere. Problema risolto.

C'è ancora un avvertimento. È forse più grande.

Avvertenza n. 2: conservazione dell'ordine

Questa è forse la ragione principale per cui le persone usano le matrici. Le matrici conservano l'ordine dei loro valori.

Devi vedere un esempio per capirlo.

numeri const = = 0,3,1,6,89,5,7,9]

Qualunque cosa tu faccia, recuperare il valore dei numeri restituirà sempre lo stesso array, con l'ordine degli input inalterato.

Che ne dici di un oggetto?

numeri const = {
 0: "Zero",
 3: "Tre",
 1 uno",
 6: "Sei",
 89: "Ottantanove",
 5: "Cinque",
 7: "Seven",
 9: "Nove"
}

L'ordine dei numeri è lo stesso dell'array precedente.

Ora, guardami, copialo e incollalo nella console del browser, quindi prova a recuperare i valori.

Il problema con gli oggetti.

Ok, potresti averlo perso. Guarda sotto:

Il problema con oggetti e ordine non conservato.

Vedi i punti salienti nell'immagine sopra. L'ordine dei valori dell'oggetto non viene restituito allo stesso modo!

Ora, a seconda del tipo di applicazione che stai creando, questo può causare problemi molto seri. Soprattutto nelle app in cui l'ordine è fondamentale.

Conosci qualche esempio di tale app?

Beh, lo faccio. Un'applicazione di chat!

Se stai rappresentando le conversazioni degli utenti come oggetto, ti preoccupi sicuramente dell'ordine in cui vengono visualizzati i messaggi!

Non vuoi che un messaggio venga inviato ieri, che mostri che è stato inviato oggi. L'ordine è importante.

Quindi, come lo risolveresti?

Soluzione n. 2:

Mantenere un array separato di ID per indicare l'ordine.

Devi averlo visto prima, ma forse non hai prestato attenzione.

Ad esempio, se avessi il seguente oggetto:

numeri const = {
  0: "Zero",
  3: "Tre",
  1 uno",
  6: "Sei",
  89: "Ottantanove",
  5: "Cinque",
  7: "Seven",
  9: "Nove"
 }

È possibile mantenere un altro array per indicare l'ordine dei valori.

numbersOrderID: [0, 3, 1, 6, 89, 5, 7, 9]

In questo modo è sempre possibile tenere traccia dell'ordine dei valori, indipendentemente dal comportamento dell'oggetto. Se è necessario aggiungere valori all'oggetto, lo si fa, ma si passa anche l'ID associato ai numeriOrderID.

È importante essere consapevoli di queste cose poiché potresti non avere sempre il controllo su alcune cose. È possibile raccogliere applicazioni con stato modellato in questo modo. E anche se non ti piace l'idea, dovresti assolutamente essere al corrente.

Per semplicità, gli ID dei messaggi per l'applicazione Skypey saranno sempre in ordine, poiché sono numerati in valori crescenti da zero verso l'alto.

Questo potrebbe non essere il caso di un'app reale. Potresti avere strani ID generati automaticamente che sembrano incomprensibili come y68fnd0a9wyb.

In tali casi, si desidera mantenere un array separato per tenere traccia dell'ordine dei valori.

Questo è tutto!

Vale la pena affermare che l'intero processo di normalizzazione dell'oggetto stato può essere riassunto come segue:

• Ogni tipo di dati dovrebbe avere la propria chiave nell'oggetto state.

• Ogni chiave deve memorizzare i singoli elementi in un oggetto, con gli ID degli elementi come chiavi e gli stessi elementi come valori.

• Eventuali riferimenti a singoli articoli devono essere fatti memorizzando l'ID dell'articolo.

• Idealmente, conservare una serie di ID per indicare l'ordinamento.

Riepilogo del design dell'oggetto statale

Ora so che questo è stato un lungo discorso sulla struttura dell'oggetto statale.

Potrebbe non sembrare importante per te adesso, ma mentre costruisci progetti scoprirai quanto può essere prezioso dare un pensiero alla progettazione del tuo stato. Ti aiuterà a eseguire le operazioni CRUD molto più facilmente, ridurrà molta logica eccessivamente complessa nei tuoi riduttori e ti aiuterà anche a trarre vantaggio dalla composizione del riduttore, un termine che descriverò più avanti in questo libro.

Volevo che comprendessi il motivo delle mie decisioni e che fossi in grado di prendere decisioni informate mentre costruisci le tue applicazioni. Credo che ora hai il potere di avere le giuste informazioni.

Detto questo, ecco una rappresentazione visiva dell'oggetto stato Skypey:

Esempio di oggetto stato per 2 contatti utente. Nell'applicazione che costruiremo, ci saranno 10 contatti con 10 messaggi iniziali.

L'immagine presuppone solo due contatti utente. Per favore, dai un'occhiata.

Costruire l'elenco degli utenti

Andando avanti, è tempo di scrivere del codice. Innanzitutto, ecco l'obiettivo di questa sezione. Per creare l'elenco degli utenti mostrato di seguito:

Dovremmo avere qualcosa del genere quando compiliamo con successo l'elenco degli utenti.

Cosa è necessario per costruire questo?

Da un livello elevato, dovrebbe essere abbastanza chiaro che all'interno del componente della barra laterale è necessario visualizzare un elenco dei contatti di un utente.

Presumibilmente, all'interno della sidebar, potresti avere qualcosa del genere:

contatti.map (contatto => )

Capito?

Si mappa su alcuni dati dei contatti dallo stato e per ogni contatto si esegue il rendering di un componente Utente.

Ma da dove provengono i dati per questo?

Idealmente, e in uno scenario del mondo reale, recupererai questi dati dal server con una chiamata Ajax. Per i nostri scopi di apprendimento, ciò comporta uno strato di complessità che possiamo evitare, per ora.

Quindi, a differenza del recupero dei dati in remoto da un server, ho creato alcune funzioni che gestiranno la creazione dei dati per l'app. Utilizzeremo questi dati statici per creare l'applicazione.

Ad esempio, esiste una variabile di contatti già creata in static-data.js, che restituirà sempre un elenco di contatti generati casualmente. Tutto quello che devi fare è importarlo nell'app. Nessuna chiamata Ajax.

Quindi, crea un nuovo file nella directory principale del progetto e chiamalo static-data.js

Copia il contenuto del contenuto qui in quel file. Lo useremo abbastanza presto.

Configurare il negozio

Esaminiamo rapidamente il processo di configurazione dell'archivio dell'app in modo da poter recuperare i dati richiesti per creare l'elenco degli utenti nella barra laterale.

Uno dei primi passi durante la creazione di un'app Redux è la configurazione del negozio Redux. Poiché è qui che verranno letti i dati, diventa indispensabile risolverli.

Quindi, installa redux dal cli con:

filato aggiungere redux

Al termine dell'installazione, creare una nuova cartella denominata store e nella directory, creare un nuovo file index.js.

Non dimenticare l'analogia di avere i principali attori Redux nelle loro stesse directory.

Come già sapete, il negozio verrà creato tramite la funzione di fabbrica createStore da redux in questo modo:

negozio / index.js

importare {createStore} da "redux";

const store = createStore (someReducer, initialState);

esporta archivio predefinito;

Il redStore createStore deve essere a conoscenza del riduttore (ricordare la relazione negozio e riduttore che ho spiegato in precedenza).

Ora modifica la seconda riga per assomigliare a questa:

const store = createStore (riduttore, {contatti});

Ora, importa il riduttore e i contatti dai dati statici:

riduttore di importazione da "../riduttori";
importare {contatti} da "../static-data";

Dal momento che in realtà non abbiamo creato alcuna directory dei riduttori, per favore fallo ora. Crea anche un file index.js con questa directory di riduttori.

Ora crea il riduttore.

riduttori / index.js

export default (state, action) => {
    stato di ritorno;
};

Un riduttore è solo una funzione che accetta stato e azione e restituisce un nuovo stato.

Se ti perdessi nella creazione del negozio, const store = createStore (riduttore, {contatti}); dovresti ricordare che il secondo argomento in createStore è lo stato iniziale dell'app.

Ho impostato questo sull'oggetto {contatti}.

Questa è una sintassi ES6, simile a questa: {contatti: contatti} con una chiave di contatti e un valore di contatti da dati statici.

Non c'è modo di sapere che ciò che abbiamo fatto è giusto. Cerchiamo di risolverlo.

In Index.js, ecco cosa dovresti avere ora:

Index.js

import React da "reagire";
importare ReactDOM da "reazioni-dom";
import "./index.css";
importa l'app da "./App";
import registerServiceWorker da "./registerServiceWorker";


ReactDOM.render (, document.getElementById ("root"));
registerServiceWorker ();

Come abbiamo fatto con il primo esempio, refactoring la chiamata ReactDOM.render in modo da trovarsi all'interno di una funzione di rendering.

const render = () => {
  return ReactDOM.render (, document.getElementById ("root"));
};

Quindi coinvolgere la funzione di rendering per rendere l'app correttamente.

render ()

Ora importa il negozio creato in precedenza ...

import store da "./store";

E assicurati che ogni volta che il negozio viene aggiornato, viene invocata la funzione di rendering.

store.subscribe (rendering);

Buono!

Ora, sfruttiamo questa configurazione.

Ogni volta che il negozio aggiorna e invoca il rendering, registriamo lo stato dal negozio.

Ecco come:

const render = () => {
  fancyLog ();
  return ReactDOM.render (, document.getElementById ("root"));
};

Basta chiamare una nuova funzione, fancyLog (), che presto scriverai.

Ecco la funzione fancyLog:

funzione fancyLog () {
  console.log ("% c reso con  ", "sfondo: viola; colore: #FFF");
  console.log (store.getState ());
}

Hmmm. Cosa ho fatto?

console.log (store.getState ()) è il bit con cui hai familiarità. Questo registrerà lo stato recuperato dal negozio.

La prima riga, console.log ("% c reso con ", "background: purple; color: #fff"); registrerà il testo, "Rendering con ...", oltre ad alcune emoji e alcuni stili CSS per renderlo distinguibile. La% c scritta prima del testo "Rendering con ..." consente di utilizzare lo stile CSS.

Basta parlare. Ecco il codice completo:

index.js

importare ReactDOM da "reazioni-dom";
import "./index.css";
importa l'app da "./App";
import registerServiceWorker da "./registerServiceWorker";
import store da "./store";

const render = () => {
  fancyLog ();
  return ReactDOM.render (, document.getElementById ("root"));
};

render ();
store.subscribe (rendering);
registerServiceWorker ();

funzione fancyLog () {
  console.log ("% c reso con  ", "background: purple; color: #fff");
  console.log (store.getState ());
}

Ecco l'oggetto stato che viene registrato.

Il nostro logger di base al lavoro.

Come puoi vedere, all'interno dell'oggetto state è presente un campo contatti che contiene i contatti disponibili per il particolare utente. La struttura dei dati è come discusso in precedenza. Ogni contatto è mappato con il proprio user_id

Abbiamo fatto progressi decenti.

Passando i dati della barra laterale tramite puntelli

Se dai un'occhiata all'intero codice ora, accetti che il punto di ingresso dell'app rimanga index.js.

Index.js esegue quindi il rendering del componente App. Il componente App è quindi responsabile del rendering dei componenti Main e Sidebar.

Affinché Sidebar abbia accesso ai dati dei contatti richiesti, passeremo i dati tramite oggetti di scena.

In App.js, recupera i contatti dallo store e passali alla sidebar in questo modo:

App.js

const App = () => {
  const {contatti} = store.getState ();

  ritorno (
    
                     ); };
L'oggetto contatti è passato con successo come oggetti di scena alla barra laterale

Come ho fatto nello screenshot sopra, ispeziona il componente Sidebar e troverai i contatti passati come prop. I contatti sono un oggetto con ID mappati su oggetti utente.

Ora possiamo procedere al rendering dei contatti.

Innanzitutto, installa Lodash dal cli:

filato aggiungere lodash

Import lodash in App.js

importare da lodash

Lo so. Il carattere di sottolineatura sembra divertente, ma è una bella convenzione. Lo amerai :)

Ora, per usare uno qualsiasi dei metodi di utilità che lodash ci offre, chiama i metodi sul carattere di sottolineatura importato, come .fakeMethod ().

Ora, fai buon uso di Lodash. Utilizzando una delle funzioni dell'utilità Lodash, l'oggetto contatti può essere facilmente convertito in un array quando viene passato come oggetti di scena.

Ecco come:

Si noti che i puntelli dei contatti ora sono un array.

Puoi leggere di più sul metodo Lodval .values ​​se vuoi. In poche parole, crea una matrice di tutti i valori chiave dell'oggetto passato.

Ora, rendiamo davvero qualcosa nella barra laterale.

Sidebar.js

import React da "reagire";
importare l'utente da "./User";
import "./Sidebar.css";

const Sidebar = ({contatti}) => {
  ritorno (
    
      {contact.map (contact => )}
    
  );
};

esportazione barra laterale predefinita;

Nel blocco di codice sopra, mappiamo sul puntello dei contatti e rendiamo un componente Utente per ogni contatto.

Per impedire la chiave di avviso di React, user_id del contatto viene utilizzato come chiave. Inoltre, ogni contatto viene passato come prop per l'utente al componente Utente.

Creazione del componente utente

Stiamo eseguendo il rendering di un componente Utente nella barra laterale, ma questo componente non esiste ancora.

Creare un file User.js e User.css nella directory principale.

Fatto?

Ora, ecco il contenuto del file User.js:

user.js

import React da "reagire";
import "./User.css";

const User = ({user}) => {
  const {nome, profilo_pic, stato} = utente;

  ritorno (
    
      {name}       
        

{name}

        

{status}

              ); }; esporta Utente predefinito;

Non lasciarti ingannare dal grosso pezzo di codice. In realtà è molto facile da leggere e capire. Dai una seconda occhiata.

Il nome, l'URL profilo_pic e lo stato dell'utente sono ottenuti dagli oggetti di scena tramite destrutturazione: const {nome, profilo_pic, stato} = utente;

Questi valori vengono quindi utilizzati nell'istruzione return per un rendering corretto, ed ecco il risultato:

Rendering della barra laterale con contatti non stirati.

Il risultato sopra è molto brutto, ma è un'indicazione che funziona!

Ora, diamo uno stile a questo.

Innanzitutto, impedire all'elenco di utenti di traboccare il contenitore della barra laterale.

Sidebar.css

.Sidebar {
...
overflow-y: scroll;
}

Inoltre, il carattere è brutto. Cambiamo questo.

Index.css

@import url ("https://fonts.googleapis.com/css?family=Nunito+Sans:400.700");

body {
 ...
  peso carattere: 400;
  famiglia di caratteri: "Nunito Sans", sans-serif;
}

Infine, gestisci la visualizzazione complessiva del componente Utente.

user.css

.Utente {
  display: flessibile;
  align-items: flex-start;
  imbottitura: 1rem;
}
.Utente: hover {
  sfondo: rgba (0, 0, 0, 0,2);
  cursore: puntatore;
}
.User__pic {
  larghezza: 50px;
  raggio di frontiera: 50%;
}
.User__details {
  display: nessuno;
}

/ * non piccoli dispositivi * /
@media (larghezza minima: 576px) {
  .User__details {
    blocco di visualizzazione;
    imbottitura: 0 0 0 1rem;
  }
  .User__details-name {
    margine: 0;
    colore: rgba (255, 255, 255, 0,8);
    dimensione carattere: 1rem;
  }
}

Dal momento che questo non è un libro CSS, sto saltando alcune delle spiegazioni sullo stile. Tuttavia, se qualcosa ti confonde, basta chiedermelo su Twitter e sarò felice di aiutarti.

Ecco!

Ecco il bellissimo display che abbiamo ora:

I contatti della barra laterale sono ben disegnati!

Sorprendente!

Siamo passati dal nulla alla visualizzazione di un bellissimo elenco di utenti sullo schermo.

Se stai programmando, ridimensiona il browser per vedere anche la splendida vista sui dispositivi mobili.

Tenere duro!

Hai domande?

È perfettamente normale avere domande.

Il modo più rapido per raggiungermi sarà quello di twittare la tua domanda via Twitter, con l'hashtag #UnderstandingRedux. In questo modo posso facilmente trovare e rispondere alla tua domanda.

Non è necessario trasmettere oggetti di scena

Dai un'occhiata alla struttura di alto livello dell'interfaccia utente di Skypey di seguito:

Una struttura di alto livello del layout Skypey

Nelle app React tradizionali (senza utilizzare l'API di contesto), è necessario passare gli oggetti di scena da a e

Passa oggetti di scena dall'app, come nelle normali app React.

Con Redux, tuttavia, non sei vincolato da questa regola.

Se un determinato componente necessita dell'accesso a un valore dall'oggetto state, puoi semplicemente raggiungere l'archivio e recuperare lo stato corrente.

Ad esempio, e

possono accedere allo store Redux senza la necessità di dipendere da

Con Redux, non devi passare oggetti di scena. Ottieni i valori richiesti direttamente dal negozio

L'unico motivo per cui non l'ho fatto qui è perché è un genitore diretto, con e

NON più di un livello nella gerarchia dei componenti.

Come vedrai nelle sezioni successive, per i componenti nidificati più in profondità nella gerarchia dei componenti, raggiungeremo direttamente l'archivio Redux per recuperare lo stato corrente.

Non è necessario tramandare oggetti di scena.

Ti piacerà il grafico qui sotto. Va anche oltre a descrivere la necessità di non tramandare oggetti di scena quando si lavora con Redux.

Struttura delle cartelle del contenitore e dei componenti

C'è un po 'di refactoring che devi fare prima di passare alla codifica dell'applicazione Skypey.

Nelle applicazioni Redux, è un modello comune suddividere i componenti in due diverse directory.

Ogni componente che comunica direttamente con Redux, sia che si tratti di recuperare lo stato dall'archivio, sia di inviare un'azione, dovrebbe essere spostato in una directory dei contenitori.

Altri componenti, quelli che non parlano con Redux, dovrebbero essere spostati in una directory dei componenti.

Bene bene bene. Perché passare attraverso la seccatura?

Per uno, il tuo codebase diventa un po 'più pulito. Inoltre, diventa più facile trovare determinati componenti purché tu sappia se parlano o meno con Redux.

Quindi andate avanti.

Dai un'occhiata ai componenti nello stato corrente dell'applicazione e rimescola di conseguenza.

Quindi non rovinare tutto, ricorda di spostare il file CSS associato ai componenti.

Ecco la mia soluzione:

  1. Crea due cartelle: contenitori e componenti.
  2. App.js tenta di recuperare i contatti dallo store. Quindi, spostare App.js e App.css nella cartella dei contenitori.
  3. Spostare Sidebar.js, Sidebar.css, Main.js e Main.css nella cartella dei componenti. Non parlano direttamente con Redux per niente.
  4. Si prega di non spostare Index.js e Index.css. Quelli sono il punto di ingresso dell'app. Lasciare quelli nella radice della directory del progetto.
  5. Spostare User.js e User.css nella directory dei contenitori. Il componente Utente NON parla ancora con Redux ma lo farà. Ricorda che quando l'app è completata, facendo clic su un utente dalla barra laterale, verranno visualizzati i loro messaggi. Di conseguenza, verrà inviata un'azione. Nelle prossime sezioni lo svilupperemo.
  6. Ormai molti URL di importazione verranno interrotti, ovvero i componenti che hanno importato questi componenti spostati. Devi cambiare il loro URL di importazione. Lascio a te questo. È una soluzione semplice :)

Ecco una soluzione di esempio per il n. 6 sopra: in App.js, modifica la barra laterale e le importazioni principali in questo:

importare la barra laterale da "../components/Sidebar";
import Main da "../components/Main";

Al contrario del primo:

import Sidebar da "./Sidebar";
import Main da "./Main";

Capito?

Ecco alcuni suggerimenti per risolvere da soli la sfida:

  1. Controllare l'istruzione di importazione Sidebar.js per il componente Utente.
  2. Controlla la dichiarazione di importazione Index.js per il componente App.
  3. Controllare l'istruzione di importazione App.js per l'archivio

Fatto ciò, Skypey funzionerà come previsto!

Refactoring per impostare lo stato iniziale dal riduttore

Innanzitutto, dai un'occhiata alla creazione del negozio in store / index.js. In particolare, considera questa riga di codice:

const store = createStore (riduttore, {contatti});

L'oggetto stato iniziale viene passato direttamente in createStore. Ricorda che il negozio è stato creato con la firma, createStore (riduttore, initialState). In questo caso, lo stato iniziale è stato impostato sull'oggetto, {contatti: contatti}

Anche se questo approccio funziona, in genere viene utilizzato per il rendering lato server (non preoccuparti se non sai cosa significa). Per ora, comprendi che questo approccio di impostazione di uno stato iniziale in createStore è più utilizzato nel mondo reale per il rendering lato server.

In questo momento, rimuovere lo stato iniziale nel metodo createStore.

Avremo lo stato iniziale dell'applicazione impostato esclusivamente dal riduttore.

Fidati di me, avrai capito.

Ecco come apparirà il file store / index.js dopo aver rimosso lo stato iniziale da createStore.

importare {createStore} da "redux";
riduttore di importazione da "../riduttori";

const store = createStore (riduttore);

esporta archivio predefinito;

Ed ecco il contenuto attuale del file riduttore / index.js:

export default (state, action) => {
  stato di ritorno;
};

Si prega di cambiarlo in questo:

importare {contatti} da "../static-data";

export default (state = {contatti}, azione) => {
  stato di ritorno;
};

Allora, cosa sta succedendo qui?

Utilizzando i parametri predefiniti ES6, abbiamo impostato il parametro state su un valore iniziale di {contatti}.

Questo è essenzialmente lo stesso di {contatti: contatti}.

Quindi, l'istruzione di stato di ritorno all'interno del riduttore restituirà questo valore, {contatti: contatti} come stato iniziale dell'applicazione.

A questo punto, l'app ora funziona, proprio come prima. L'unica differenza qui è che lo stato iniziale dell'applicazione è ora gestito da Reducer.

Continuiamo il refactoring.

Composizione del riduttore

In tutte le app che abbiamo creato finora, abbiamo usato un solo riduttore per gestire l'intero stato delle applicazioni.

Qual è l'implicazione di questo?

È come avere un solo cassiere in tutta la sala della banca. Quanto è scalabile?

Anche se il Cassiere può svolgere tutto il lavoro in modo efficace, può essere più gestibile - e forse una migliore esperienza del cliente - avere più di un Cassiere nella hall della banca.

Qualcuno deve occuparsi di tutti ed è molto lavoro per una sola persona!

Lo stesso vale per le tue applicazioni Redux.

È comune avere più riduttori nella propria applicazione anziché un riduttore che gestisce tutte le operazioni dello stato. Questi riduttori vengono quindi combinati in uno.

Ad esempio, potrebbero esserci 5 o 10 cassieri nella hall della banca, ma tutti combinati servono tutti a uno scopo. Ecco come funziona anche questo.

Considera l'oggetto stato dell'app Hello World che abbiamo creato in precedenza.

{
  tecnologia: "Reagisci"
}

Abbastanza semplice.

Tutto ciò che abbiamo fatto è stato avere un riduttore che gestisse gli aggiornamenti dello stato.

Tuttavia, considera l'oggetto stato dell'applicazione Skypey più complessa:

Avere un singolo riduttore a gestire l'intero oggetto stato è fattibile, ma non è l'approccio migliore.

Invece di avere l'intero oggetto gestito da un riduttore, cosa succederebbe se avessimo un riduttore a gestire un campo nell'oggetto stato?

Ti piace una mappatura uno a uno?

Vedi cosa ci facciamo lì? Presentazione di più cassieri!

La composizione del riduttore richiede che un singolo riduttore gestisca l'aggiornamento dello stato per un singolo campo nell'oggetto stato.

Ad esempio, per il campo messaggi, hai un messageReducer. Per un campo contatti, hai anche un riduttore di contatti e così via.

Una cosa più importante da sottolineare è che il valore di ritorno di ciascuno dei riduttori è esclusivamente per il campo che rappresentano.

Quindi, se avessi i messaggi Reducer scritti in questo modo:

export const message_Reducer (stato = {}, azione) {
  stato di ritorno
}

Lo stato restituito qui non è lo stato dell'intera applicazione.

No.

È solo il valore del campo messaggi.

Lo stesso vale per gli altri riduttori.

Capito?

Vediamo questo in pratica e in che modo questi riduttori sono combinati esattamente per un unico scopo.

Refactoring Skypey per utilizzare più riduttori

Ricordi come ho parlato di più riduttori che gestiscono ciascun campo nell'oggetto stato?

In questo momento, puoi dire che avremo il seguente riduttore multiplo come mostrato nella figura seguente:

Ora, per ogni campo nell'oggetto stato, creeremo un riduttore corrispondente. Quelli attuali in questa fase sono, contatti e utente.

Vediamo come questo influisce prima sul nostro codice. Quindi farò un passo indietro per spiegare come funziona di nuovo.

Dai un'occhiata a riduzione / index.js:

importare {contatti} da "../static-data";

esportazione predefinita (stato = contatti, azione) => {
  stato di ritorno;
};

Rinomina questo file in contatti.js.

Questo diventerà il riduttore di contatti.

Creare un file user.js nella directory di riduttori.

Questo sarà il riduttore dell'utente.

Ecco il contenuto:

importare {generateUser} da "../static-data";
export funzione utente predefinita (state = generateUser (), azione) {
  stato di ritorno;
}

Ancora una volta, ho creato una funzione generateUser per generare alcune informazioni utente statiche.

Utilizzando i parametri predefiniti ES6, lo stato iniziale viene impostato sul risultato dell'invocazione di questa funzione. Pertanto, lo stato di ritorno ora restituirà un oggetto utente.

In questo momento, abbiamo due diversi riduttori. Combiniamoli per il bene superiore :)

  • Creare un file index.js nella directory di riduttori

Innanzitutto, importare i due riduttori, l'utente e i contatti:

importa l'utente da "./user";
importare contatti da "./contatti";

Per combinare questi riduttori, abbiamo bisogno della funzione di aiuto combinare i riduttori di Redux

Importalo in questo modo:

importare {combinReducers} da "redux";

Ora index.js esporterà la combinazione di entrambi i riduttori in questo modo:

export default combinReducers ({
  utente,
  contatti,
});

Si noti che la funzione combinReducers accetta un oggetto. Un oggetto la cui forma è esattamente come l'oggetto stato dell'applicazione.

Il blocco di codice è lo stesso di questo:

export default combinReducers ({
  utente: utente,
  contatti: contatti
})

L'oggetto ha chiavi utente e contatti, proprio come l'oggetto stato che abbiamo in mente.

E i valori di queste chiavi?

I valori provengono dai riduttori!

È importante capirlo. Va bene?

Mi sono perso. Come funziona di nuovo?

Vorrei fare un passo indietro e spiegare come funziona di nuovo la composizione del riduttore. Questa volta, da una prospettiva diversa.

Considera l'oggetto JavaScript qui sotto:

const state = {
  utente: "me",
  messaggi: "ciao",
  contatti: ["nessuno", "khalid"],
  activeUserId: 1234
}

Ora, supponiamo che invece di avere i valori delle chiavi hardcoded, volessimo che fosse rappresentato da chiamate di funzione. Potrebbe apparire così:

const state = {
   utente: getUser (),
   messaggi: getMsg (),
   contatti: getContacts (),
   activeUserId: getID ()
}

Ciò presuppone che getUser () restituirà anche il valore precedente, "me". Lo stesso vale per le altre funzioni sostituite.

Stai ancora seguendo?

Ora, rinominiamo queste funzioni.

const state = {
   utente: utente (),
   messaggi: messaggi (),
   contatti: contatti (),
   activeUserId: activeUserId ()
}

Ora, le funzioni hanno nomi identici ai corrispondenti tasti oggetto. Invece di getUser (), ora abbiamo user ().

Diventiamo fantasiosi.

Immagina che esistesse una certa funzione di utilità importata da alcune librerie. Chiamiamo questa funzione, killerFunction.

Ora, killerFunction rende possibile farlo:

const state = killerFunction ({
   utente: utente,
   messaggi: messaggi,
   contatti: contatti,
   activeUserId: activeUserId
})

Cosa è cambiato?

Invece di invocare ciascuna delle funzioni, basta scrivere i nomi delle funzioni. killerFunction si occuperà di invocare le funzioni.

Ora usando ES6, possiamo semplificare ulteriormente il codice:

const state = killerFunction ({
   utente,
   messaggi,
   contatti,
   activeUserId
})

È lo stesso del blocco di codice precedente. Supponendo che le funzioni siano nell'ambito e abbiano lo stesso nome (identificativo) della chiave dell'oggetto.

Capito?

Ora, questo è un po 'come funziona il combinatore rosso di Redux.

I valori di ogni chiave nell'oggetto stato verranno ottenuti dal riduttore. Non dimenticare che un riduttore è solo una funzione.

Proprio come killerFunction, combinReducers è in grado di assicurarsi che i valori siano ottenuti invocando le funzioni passate.

Tutte le chiavi e i valori messi insieme comporteranno quindi l'oggetto stato dell'applicazione.

Questo è tutto!

Un punto importante da ricordare sempre è che quando si utilizza CombinaReduttori, il valore restituito da ciascun riduttore non è lo stato dell'applicazione.

È solo il valore della chiave particolare che rappresentano nell'oggetto stato!

Ad esempio, il riduttore utente restituisce il valore per la chiave utente nello stato. Allo stesso modo, il riduttore di messaggi restituisce il valore per la chiave di messaggi nello stato.

Ora, ecco il contenuto completo di riduttori / index.js:

importare {combinReducers} da "redux";
importa l'utente da "./user";
importare contatti da "./contatti";

export default combinReducers ({
  utente,
  contatti
});

Ora, se controlli i log, troverai utente e contatti proprio lì nell'oggetto state.

Costruire lo schermo vuoto

In questo momento, il componente principale visualizza solo il testo, roba principale. Questo non è quello che vogliamo.

L'obiettivo finale è mostrare una schermata vuota, ma mostrare i messaggi dell'utente quando si fa clic su un contatto.

Costruiamo lo schermo vuoto.

Per questo, avremo bisogno di un nuovo componente chiamato, Empty.js. Mentre ci sei, crea anche un file CSS corrispondente, Empty.css.

Si prega di creare questi nella directory dei componenti.

renderà il markup per lo schermo vuoto. Per fare ciò, richiederà un determinato prop utente.

Sicuramente, l'utente deve essere passato dallo stato dell'applicazione. Non dimenticare la struttura generale dell'oggetto stato che abbiamo risolto in precedenza:

Quindi, ecco il contenuto corrente del componente

:

import React da "reagire";
import "./Main.css";

const Main = () => {
  return 
Main Stuff
; }; esportazione predefinita Main;

Restituisce solo il testo, Main Stuff.

Il componente è responsabile della visualizzazione del componente quando nessun utente è attivo. Non appena si fa clic su un utente, esegue il rendering delle conversazioni dell'utente selezionato. Questo potrebbe essere rappresentato da un componente, .

Per far funzionare questo render e per rendere

o o , dobbiamo tenere traccia di alcuni activeUserId.

Ad esempio, per impostazione predefinita activeUserId sarà null, quindi verrà visualizzato .

Tuttavia, non appena si fa clic su un utente, activeUserId diventa l'id_utente del contatto selezionato. Ora,

renderà il componente .

Splendido, no?

Perché questo funzioni, manterremo un nuovo campo nell'oggetto state, activeUserId

Ormai, dovresti già conoscere il trapano. Per aggiungere un nuovo campo all'oggetto stato, avremo questo set nei riduttori.

Creare un nuovo file, activeUserId.js nella cartella dei riduttori.

Ed ecco il contenuto del file:

riduttori / activeUserId.js

esporta funzione predefinita activeUserId (state = null, action) {
  stato di ritorno;
}

Per impostazione predefinita, restituisce null.

Ora, aggancia questo riduttore appena creato alla chiamata del metodo combinReducer in questo modo:

...
 import activeUserId da "./activeUserId";
 
export default combinReducers ({
  utente,
  contatti,
  activeUserId
});

Ora, se controlli i log, troverai activeUserId proprio lì nell'oggetto state.

Andiamo avanti.

In App.js, recupera l'utente e activeUserId dallo store, in questo modo:

const {contatti, utente, activeUserId} = store.getState ();

Ciò che avevamo in precedenza era questo:

const {contatti} = store.getState ();

Ora passa questi valori come oggetti di scena al componente

.

Ciò che avevamo in precedenza era questo:

Ora, abbiamo la logica di rendering perfezionata in

prima:

import React da "reagire";
import "./Main.css";

const Main = () => {
  return 
Main Stuff
; }; esportazione predefinita Main;

adesso:

import React da "reagire";
import "./Main.css";
import Vuoto da "../components/Empty";
importare ChatWindow da "../components/ChatWindow";

const Main = ({user, activeUserId}) => {
  const renderMainContent = () => {
    if (! activeUserId) {
      return ;
    } altro {
      return ;
    }
  };
  return 
{renderMainContent ()}
; }; esportazione predefinita Main;

Ciò che è cambiato non è difficile da capire. user e activeUserId sono ricevuti come oggetti di scena. L'istruzione return all'interno del componente ha la funzione renderMainContent invocata.

Tutto ciò che renderMainContent fa è verificare se activeUserId non esiste. In caso contrario, rende lo schermo vuoto. Se esiste, viene visualizzata la finestra di chat.

Grande!

Non abbiamo ancora creato i componenti Empty e ChatWindow.

Perdonami, ho intenzione di incollare molto codice contemporaneamente.

Modifica il file Empty.js per contenere questo:

import React da "reagire";
import "./Empty.css";

const Vuoto = ({utente}) => {
  const {nome, profilo_pic, stato} = utente;
  const first_name = name.split ("") [0];

  ritorno (
    
      

Benvenuto, {first_name}

      {name}       

         Stato: {stato}                     

        Cerca qualcuno con cui iniziare a chattare o vai a Contatti per vedere chi         è disponibile               ); }; export default Vuoto;

Ops. Cos'è tutto quel codice ???

Fai un passo indietro, non è così complesso come sembra.

Il componente accetta un oggetto prop. Questo prop utente è un oggetto che ha la seguente forma:

{
nome,
e-mail,
foto del profilo,
stato,
ID utente:
}

Utilizzando la sintassi destrutturante ES6, prendere il nome, il profilo_pic e lo stato dall'oggetto utente:

const {nome, profilo_pic, stato} = utente;

Per la maggior parte degli utenti, il nome contiene due parole come Ohans Emmanuel. Prendi la prima parola e assegnala alla variabile first_name in questo modo:

const first_name = name.split ("") [0];

La dichiarazione di ritorno sputa appena un pezzo di markup.

Vedrai il risultato molto presto.

Prima di procedere, non dimentichiamoci di creare un componente ChatWindow nella directory dei contenitori.

ChatWindow sarà responsabile della visualizzazione delle conversazioni per un contatto utente attivo e farà molto parlare direttamente con Redux!

In ChatWIndow.js scrivi quanto segue:

import React da "reagire";

const ChatWindow = ({activeUserId}) => {
  ritorno (
    
Conversazione per ID utente: {activeUserId}
  ); }; esporta ChatWindow predefinita;

Torneremo a perfezionare questo. In questo momento, questo è abbastanza buono.

Salva tutte le modifiche che abbiamo apportato finora ed ecco cosa ho ottenuto!

Viene visualizzata la schermata vuota ustyled.

Dovresti avere anche qualcosa di molto simile.

Lo schermo vuoto funziona, ma è brutto e nessuno ama le app brutte.

Ho scritto il CSS per il componente .

Empty.css

.Vuoto {
  display: flessibile;
  direzione flessibile: colonna;
  align-items: center;
  giustificare-contenuto: centro;
  altezza: 100%;
}
.Empty__name {
  colore: #fff;
}
.Empty__status,
.Empty__info {
  imbottitura: 1rem;
}
.Empty__status {
  colore: rgba (255, 255, 255, 0.9);
  bordo inferiore: 1px solido rgba (255, 255, 255, 0,7);
}
.Empty__img {
  raggio di frontiera: 50%;
  margine: 2rem 0;
}
.Empty__btn {
  imbottitura: 1rem;
  margine: 1rem 0;
  peso carattere: grassetto;
  dimensione carattere: 1,2rem;
  raggio del bordo: 30 px;
  contorno: 0;
}
.Empty__btn: hover {
  sfondo: rgba (255, 255, 255, 0,7);
  cursore: puntatore;
}

Solo un buon vecchio CSS. Scommetto che puoi capire gli stili.

Ora, ecco il risultato di ciò:

Con alcuni CSS, lo schermo vuoto prende rapidamente vita.

Ecco il risultato con gli Devtools ancorati:

Stai bene? Il risultato finora!

Ora, sicuramente sembra buono!

Costruire la finestra di chat

Ecco l'intera finestra di chat.

Dai un'occhiata alla logica all'interno del componente

. verrà visualizzato solo quando activeUserId è presente.

Al momento, activeUserId è impostato su null.

Dobbiamo assicurarci che activeUserId sia impostato ogni volta che si fa clic su un contatto.

Cosa ne pensi?

Dobbiamo inviare un'azione, giusto?

Si!

Definiamo la forma dell'azione.

Ricorda che un'azione è solo un oggetto con un campo tipo e un payload.

Il campo del tipo è obbligatorio, mentre puoi chiamare il payload come preferisci. il payload è comunque un buon nome. Anche molto comune.

Quindi, ecco una rappresentazione dell'azione:

{
  digitare: "SET_ACTION_ID",
  payload: user_id
}

Il tipo o il nome dell'azione si chiamerà SET_ACTION_ID.

Nel caso ti stavi chiedendo, è abbastanza comune usare la custodia del serpente con lettere maiuscole in tipi di azioni come SET_ACTION_ID e non setactionid o set-action-id.

Inoltre, il payload dell'azione sarà user_id dell'utente da impostare come attivo.

Ora inviamo le azioni in seguito all'interazione dell'utente.

Poiché è la prima volta che inviamo azioni in questa applicazione, crea una nuova directory di azioni. Mentre ci sei, crea anche una cartella delle costanti.

Nella cartella delle costanti, crea un nuovo file, action-types.js.

Questo file ha la sola responsabilità di mantenere le costanti del tipo di azione. Spiegherò perché questo è importante, a breve.

Scrivi quanto segue in action-types.js.

costanti / action-types.js

export const SET_ACTIVE_USER_ID = "SET_ACTIVE_USER_ID";

Quindi perchè è importante?

Per capirlo, dobbiamo esaminare dove vengono utilizzati i tipi di azione in un'applicazione Redux.

Nella maggior parte delle applicazioni Redux, verranno visualizzate in due punti.

1. Il riduttore

Quando si passa al tipo di azione nei riduttori:

switch (action.type) {
  caso "WITHDRAW_MONEY":
     fare qualcosa();
     rompere;
}

2. Il creatore di azioni

All'interno del creatore dell'azione, scrivi anche un codice simile al seguente:

export const seWithdrawAmount = amount => ({
  tipo: "WITHDRAW_MONEY,
  payload: importo
})

Ora, dai un'occhiata alla logica del riduttore e del creatore dell'azione sopra. Cosa è comune ad entrambi?

La stringa "WITHDRAW_MONEY"!

Man mano che la tua applicazione cresce e hai molte di queste stringhe che volano intorno al luogo, tu (o qualcun altro) un giorno potresti commettere l'errore di scrivere "WITDDRAW_MONEY" o "WITHDRAW_MONY" invece di "WITHDRAW_MONEY_"

Il punto che sto cercando di chiarire è che l'uso di stringhe non elaborate come questa semplifica il refuso. Per esperienza, i bug che provengono dai refusi sono estremamente fastidiosi. Potresti finire per cercare così a lungo, solo per vedere il problema è stato causato da una piccola mancanza da parte tua.

Impedisci a te stesso di dover affrontare questa seccatura.

Un buon modo per farlo è archiviare le stringhe come costanti in un file separato. In questo modo, invece di scrivere le stringhe non elaborate in più punti, è sufficiente importare la stringa dalla costante dichiarata.

Dichiarate le costanti in un posto, ma potete usarle nel maggior numero di posti possibile. Nessun errore di battitura!

Questo è esattamente il motivo per cui abbiamo creato il file constants / action-types.js.

Ora creiamo il creatore dell'azione.

azione / index.js

importare {SET_ACTIVE_USER_ID} da "../constants/action-types";

export const setActiveUserId = id => ({
  tipo: SET_ACTIVE_USER_ID,
  payload: id
});

Come puoi vedere, ho importato la stringa del tipo di azione dalla cartella delle costanti. Proprio come ho spiegato prima.

Ancora una volta, il creatore di azioni è solo una funzione. Ho chiamato questa funzione setActiveUserId. Riceverà un ID di un utente e restituirà l'azione (ovvero l'oggetto) con il tipo e il payload impostati correttamente.

Con questo in atto, ciò che resta è inviare questa azione quando un utente fa clic su un utente e fare qualcosa con l'azione inviata all'interno dei nostri riduttori.

Continuiamo a muoverci.

Dai un'occhiata al componente User.js.

La prima riga dell'istruzione return è un div con il nome della classe, Utente:

Questo è il posto giusto per impostare il gestore di clic. Non appena si fa clic su questo div, invieremo l'azione che abbiamo appena creato.

Quindi, ecco il cambiamento:

E la funzione handleUserClick è proprio qui:

function handleUserClick ({user_id}) {
  store.dispatch (setActiveUserId (user_id));
}

Dove setActiveUserId è stato importato da dove? Il creatore dell'azione!

importare {setActiveUserId} da "../actions";

Ora, sotto tutto il codice User.js che dovresti avere a questo punto:

contenitori / user.js

import React da "reagire";
import "./User.css";
importare store da "../store";
importare {setActiveUserId} da "../actions";

const User = ({user}) => {
  const {nome, profilo_pic, stato} = utente;

  ritorno (
    
      {name}       
        

{name}

        

{status}

              ); }; function handleUserClick ({user_id}) {   store.dispatch (setActiveUserId (user_id)); } esporta Utente predefinito;

Per inviare l'azione, ho anche dovuto importare il negozio e ho chiamato il metodo store.dispatch ().

Si noti inoltre che ho usato la sintassi destrutturante ES6 per afferrare l'id_utente dall'argomento utente in handleUserClick.

Se stai programmando, come ti consiglio, fai clic su uno dei contatti dell'utente e controlla i registri. È possibile aggiungere un registro della console a handleUserClick in questo modo:

function handleUserClick ({user_id}) {
  console.log (user_id);
  store.dispatch (setActiveUserId (user_id));
}

Troverai l'ID utente registrato del contatto utente.

Come avrai già notato, l'azione viene inviata, ma nulla cambia sullo schermo. ActiveUserId non è impostato nell'oggetto state. Questo perché in questo momento i riduttori non sanno nulla dell'azione inviata.

Risolviamo questo problema, ma non dimenticare di rimuovere console.log (user_id) dopo aver ispezionato i log.

Dai un'occhiata al riduttore activeUserId:

esporta funzione predefinita activeUserId (state = null, action) {
  stato di ritorno;
}

riduttore / activeUserId.js

importare {SET_ACTIVE_USER_ID} da "../constants/action-types";
esporta funzione predefinita activeUserId (state = null, action) {
  switch (action.type) {
    caso SET_ACTIVE_USER_ID:
      restituire action.payload;
    predefinito:
      stato di ritorno;
  }
}

Dovresti capire cosa sta succedendo qui.

La prima riga importa la stringa, SET_ACTIVE_USER_ID.

Quindi controlliamo se l'azione passata è di tipo SET_ACTIVE_USER_ID. In caso affermativo, il nuovo valore di activeUserId è impostato su action.payload.

Non dimenticare che il payload dell'azione contiene l'id_utente del contatto dell'utente.

Vediamo questo in azione. Funziona come previsto?

Sì!

Evviva! Abbiamo funzionato per ora;)

Ora, il componente ChatWindow viene visualizzato con il set activeUserId corretto.

Come promemoria, è importante ricordare che con la composizione del riduttore, il valore restituito di ciascun riduttore è il valore del campo di stato che rappresentano e non l'intero oggetto di stato.

Rompere la finestra di chat in componenti più piccoli

Dai un'occhiata a come appare la finestra di chat completata:

Ecco l'intera finestra di chat.

Per un approccio di sviluppo più sano, l'ho suddiviso in tre sottocomponenti, Intestazione, Chat e MessageInput:

I componenti secondari, Intestazione, Chat e MesageInput

Quindi, al fine di completare il componente chatWindow, costruiremo questi tre componenti secondari. Li comporremo quindi per formare il componente chatWindow.

Pronto?

Cominciamo con il componente Header.

Il contenuto corrente del componente chatWindow è questo:

import React da "reagire";

const ChatWindow = ({activeUserId}) => {
  ritorno (
    
Conversazione per ID utente: {activeUserId}
  ); }; esporta ChatWindow predefinita;

Non molto utile.

Aggiorna il codice a questo:

import React da "reagire";
importare store da "../store";
importa l'intestazione da "../components/Header";

const ChatWindow = ({activeUserId}) => {
  const state = store.getState ();
  const activeUser = state.contacts [activeUserId];

  ritorno (
    
              ); }; esporta ChatWindow predefinita;

Cosa è cambiato?

Ricorda che activeUserId viene passato come oggetti di scena nel componente ChatWindow.

Ora, invece di eseguire il rendering del testo, Conversazione per ID utente: ..., visualizza il componente Intestazione.

Il componente Header non può essere visualizzato correttamente senza avere la conoscenza dell'utente cliccato. Perché?

Il nome e lo stato visualizzati nell'intestazione sono quelli dell'utente cliccato.

Le informazioni nell'intestazione sono quelle del contatto cliccato.

Per tenere traccia dell'utente attivo, viene creata una nuova variabile, activeUser e il valore recuperato dall'oggetto state in questo modo: const activeUser = state.contacts [activeUserId].

Come funziona?

Innanzitutto, prendiamo lo stato dal negozio Redux: const state = store.getState ().

Ora, ricorda che ogni contatto dell'utente dell'applicazione è memorizzato nel campo dei contatti. Inoltre, ogni utente è mappato dal proprio user_id.

Pertanto, l'utente attivo può essere recuperato recuperando l'utente con il campo ID corrispondente dall'oggetto contatti: state.contacts [activeUserId].

Tutto bene?

A questo punto dobbiamo costruire il componente Header renderizzato.

Creare i file, Header.js e Header.css nella directory dei componenti.

Il contenuto di Header.js è semplice. Ecco qui:

import React da "reagire";
import "./Header.css";

intestazione funzione ({utente}) {
  const {nome, stato} = utente;
  ritorno (
    
      

{nome}

      

{status}

       ); } esporta intestazione predefinita;

È un componente funzionale senza stato che esegue il rendering di un elemento di intestazione e dei tag h1 e p per contenere il nome e lo stato dell'utente attivo.

Ricorda che l'utente attivo è l'utente cliccato dalla barra laterale.

Gli stili per il componente sono ugualmente semplici. Eccoli:

.Intestazione {
  imbottitura: 1rem 2rem;
  bordo inferiore: 1px solido rgba (189, 189, 192, 0,2);
}
.Header__name {
  colore: #fff;
}

Ora, abbiamo preso a calci questo bambino!

Stai bene!

Sorprendente. Se sei ancora qui, stai andando alla grande!

Passiamo alla creazione del componente .

Il componente Chat da creare

Il componente è essenzialmente un elenco di rendering delle conversazioni di un utente.

Quindi, da dove prendiamo queste conversazioni?

Sì, dallo stato dell'applicazione.

Come ho spiegato in precedenza, un'app del mondo reale recupererà le conversazioni dell'utente da un server. Tuttavia, il mio approccio all'apprendimento di Redux è che elimini quante più complessità possibile quando apprendi i fondamenti.

A tal fine, non ci sarà alcuna risorsa di recupero server qui. Collegheremo i dati utilizzando alcune funzioni di supporto che ho creato per la generazione casuale di dati utente.

Cominciamo collegando i dati richiesti allo stato dell'applicazione.

Il processo è lo stesso che abbiamo già fatto più volte.

  1. Crea un riduttore
  2. Utilizzando ES6, aggiungere un valore di parametro predefinito al riduttore
  3. Includere il riduttore nella chiamata della funzione CombinaReduttori.

Ci proverai prima di passare alla mia soluzione?

Ecco che arriva la mia soluzione.

Creare un nuovo file, message.js nella directory dei riduttori. Questo sarà il riduttore dei messaggi.

Ecco il contenuto del riduttore di messaggi.

riduttori / messages.js

import {getMessages} da "../static-data";

esporta messaggi funzione predefiniti (state = getMessages (10), azione) {
  stato di ritorno;
}

Per generare messaggi casuali, ho importato la funzione getMessages da dati statici

Questa funzione richiede un importo, rappresentato da un numero. La funzione getMessages genererà quindi tale quantità di messaggi per ciascun contatto dell'utente.

Ad esempio, getMessages (10) genererà 10 messaggi per contatto dell'utente.

Ora, includi il riduttore nella chiamata della funzione CombinaReduttori in riduttori / index.js

riduttori / index.js

importa messaggi da "./messages";

export default combinReducers ({
  utente,
  messaggi,
  contatti,
  activeUserId
});

In questo modo includerà un campo messaggi nell'oggetto stato.

Ecco uno sguardo ai registri. Ora troverai i messaggi come mostrato di seguito:

I messaggi ora esistono nell'oggetto stato.

Con questo in atto, possiamo tranquillamente riprendere a costruire il componente Chat.

Se non lo hai già fatto, crea i file, Chats.js e Chats.css nella directory dei componenti.

Ora importa Chat e visualizzalo sotto il componente

in ChatWindow.

contenitori / ChatWindow.js

...
importa le chat da "../components/Chats";
...
ritorno (
    
                     );

Il componente prenderà l'elenco dei messaggi dall'oggetto stato, li mapperà e li renderà meravigliosamente.

Ricorda che i messaggi passati in Chat sono specificamente i messaggi per l'utente attivo!

Mentre state.messages contiene tutti i messaggi per ogni contatto dell'utente, state.messages [activeUserId] recupererà i messaggi per l'utente attivo.

Questo è il motivo per cui ogni conversazione è mappata sull'ID utente dell'utente - per un facile recupero come abbiamo fatto.

Prendi i messaggi dell'utente attivo e passali come oggetti di scena in Chat.

contenitori / ChatWindow.js

...
importa le chat da "../components/Chats";
...
const activeMsgs = state.messages [activeUserId];

ritorno (
    
                     );

Ora, ricorda che i messaggi di ciascun utente sono un oggetto gigante con ogni messaggio che ha un campo numerico:

Dai un'occhiata al campo messaggi ancora una volta

Per semplificare l'iterazione e il rendering, lo convertiremo in un array. Proprio come abbiamo fatto con l'elenco degli utenti nella barra laterale.

Per questo, avremo bisogno di Lodash.

contenitori / ChatWindow.js

...
importare da "lodash";
importa le chat da "../components/Chats";
...
const activeMsgs = state.messages [activeUserId];

ritorno (
    
                     );

Ora, invece di passare activeMsgs, passiamo in _.values ​​(activeMsgs).

C'è ancora un passaggio importante prima di visualizzare i risultati.

Il componente Chat non è stato creato.

In Chats.js, scrivi quanto segue. Spiegherò dopo.

contenitori / Chat.js

import React, {Component} da "reagire";
import "./Chats.css";

const Chat = ({message}) => {
  const {text, is_user_msg} = messaggio;
  ritorno (
     {testo} 
  );
};

class Chats estende Component {
  render () {
    ritorno (
      
        {this.props.messages.map (message => (                    ))}            );   } } esporta chat predefinite;

Non è troppo da capire, ma spiegherò cosa sta succedendo.

Innanzitutto, dai un'occhiata al componente Chat. Noterai che ho usato un componente di classe qui. Vedrai perché in seguito.

Nella funzione di rendering, mappiamo sui puntelli dei messaggi e per ogni messaggio restituiamo un componente Chat.

Il componente Chat è semplicissimo:

const Chat = ({message}) => {
  const {text, is_user_msg} = messaggio;
  ritorno (
     {testo} 
  );
};

Per ogni messaggio che viene passato, il contenuto del testo del messaggio e il flag is_user_msg vengono entrambi catturati usando la sintassi destrutturante ES6, const {text, is_user_msg} = message;

La dichiarazione di ritorno è più interessante.

Viene visualizzato un tag span semplice.

Elimina un po 'della magia di JSX, ed ecco la semplice forma di ciò che viene reso:

 {text} 

Il contenuto testuale del messaggio è racchiuso in un elemento span. Semplice.

Tuttavia, dobbiamo distinguere tra il messaggio dell'utente dell'applicazione e il messaggio del contatto.

Non dimenticare che avviene una conversazione con almeno due persone che inviano messaggi avanti e indietro.

Se il messaggio che viene visualizzato è il messaggio dell'utente, vogliamo che il markup del rendering sia questo:

 {text} 

E se no, vogliamo questo:

 {text} 

Nota che ciò che è cambiato è la classe is-user-msg che viene attivata.

In questo modo possiamo dare uno stile specifico al messaggio dell'utente usando il selettore css mostrato di seguito:

.Chat.is-user-msg {

}

Quindi, questo è il motivo per cui abbiamo alcuni fantasiosi JSX per il rendering dei nomi delle classi in base alla presenza o all'assenza del flag is_user_msg.

 {testo} 

La vera salsa è questa:

$ {is_user_msg? "is-user-msg": “”

Questo è l'operatore ternario proprio lì!

Puoi dare un senso a tutto il codice all'interno di containers / Chats.js ora, eh?

Ecco il risultato finora.

Il risultato attuale delle nostre iterazioni. Ha ancora bisogno di un po 'di lavoro.

I messaggi sono renderizzati ma non sembra così bello. Questo perché tutti i messaggi sono resi nei tag span.

Poiché i tag di span sono elementi incorporati, tutti i messaggi vengono visualizzati in una linea continua, con un aspetto schiacciato.

Qui è dove brilla il mio CSS homeboy.

Cospargiamo un po 'di bontà CSS e iniziamo questa festa :)

A partire dalla finestra di chat, creare un nuovo file, ChatWindow.css nella directory dei contenitori.

Non dimenticare di importarlo in ChatWindow.js in questo modo: import "./ChatWindow.css"

Scrivi qui:

.ChatWindow {
    display: flessibile;
    direzione flessibile: colonna;
    altezza: 100 vh;
}

Questo farà in modo che ChatWindow occupi tutta l'altezza disponibile, 100vh. L'ho anche trasformato in un contenitore flessibile in modo da poter usare alcuni oggetti flessibili mentre allineavo i suoi elementi, vale a dire Intestazione, Chat e Messaggio.

Puoi vedere la ChatWindow con un bordo rosso qui sotto:

ChatWindow - non è ancora il migliore dei look.

Passiamo allo stile dei messaggi di chat.

Componenti / Chats.css

.Chats {
  flex: 1 1 0;
  display: flessibile;
  direzione flessibile: colonna;
  align-items: flex-start;
  larghezza: 85%;
  margine: 0 auto;
  overflow-y: scroll;
}
.Chiacchierare {
  margine: 1rem 0;
  colore: #fff;
  imbottitura: 1rem;
  sfondo: gradiente lineare (90 gradi, # 1986d8, # 7b9cc2);
  larghezza massima: 90%;
  bordo in alto a destra raggio: 10px;
  bordo in basso a destra raggio: 10px;
}
.Chat.is-user-msg {
  margine sinistro: auto;
  sfondo: # 2b2c33;
  border-top-right-radius: 0;
  bordo in basso a destra raggio: 0;
  bordo superiore sinistro raggio: 10px;
  bordo in basso a sinistra raggio: 10px;
}

@media (larghezza minima: 576px) {
  .Chiacchierare {
    larghezza massima: 60%;
  }
}

Perbacco! Sembra già così bello!

Quanto è bello :)

Permettetemi di spiegare alcune delle dichiarazioni sullo stile di importanza presenti.

Con flex: 1 1 0, .Chats è fatto per crescere (occupare spazio disponibile) e ridursi di conseguenza all'interno di ChatWindow.

.Chat è anche fatto di un contenitore flessibile con display: flex. Impostando flex-direction: colonna tutti i messaggi di chat sono allineati verticalmente. Non sono più elementi incorporati ma oggetti flessibili!

Alle chat che non sono quelle dell'utente viene assegnato un gradiente di sfondo bluastro con sfondo: gradiente lineare (90 gradi, # 1986d8, # 7b9cc2);

Questo viene sovrascritto se il messaggio è dell'utente:

.Chat.is-user-msg {
sfondo: # 2b2c33;
}

Credo che tu possa dare un senso a tutto il resto.

Fin qui tutto bene!

Sono davvero entusiasta di quanto siamo arrivati ​​lontano. Un ultimo passo e la finestra di chat è completamente costruita!

Costruiamo il componente Input messaggio.

Abbiamo dovuto costruire componenti più difficili. Questo non sarà difficile da costruire.

Tuttavia, c'è un punto da considerare.

Il componente di input sarà un componente controllato. Pertanto memorizzeremo il valore di input nell'oggetto stato dell'applicazione.

Per questo, avremo bisogno di un nuovo campo chiamato digitando l'oggetto state.

Mettiamolo qui.

Per le nostre considerazioni, ogni volta che un utente digita, invieremo un tipo di azione SET_TYPING_VALUE.

Assicurati di aggiungere questa costante nel file constants / action-types.js:

export const SET_TYPING_VALUE = "SET_TYPING_VALUE";

Inoltre, la forma dell'azione inviata sarà simile a questa:

{
   tipo: SET_TYPING_VALUE,
   payload: "valore di input"
}

Dove il payload dell'azione è il valore digitato nell'input. Creiamo un creatore di azioni per gestire la creazione di questa azione:

azioni / index.js

import {
  SET_ACTIVE_USER_ID,
  SET_TYPING_VALUE
} da "../constants/action-types";

...
export const setTypingValue = value => ({
  tipo: SET_TYPING_VALUE,
  payload: valore
})

Ora creiamo un nuovo riduttore di battitura, che prenderà in considerazione questa azione creata.

riduttori / typing.js

importare {SET_TYPING_VALUE} da "../constants/action-types";

digita la funzione predefinita di esportazione (state = "", azione) {
  switch (action.type) {
    caso SET_TYPING_VALUE:
      restituire action.payload;
    predefinito:
      stato di ritorno;
  }
}

Il valore predefinito per il campo di digitazione verrà impostato su una stringa vuota.

Tuttavia, quando viene inviata un'azione con tipo SET_TYPING_VALUE, verrà restituito il valore nel payload.

gestire il tipo SET_TYPING_VALUE

In caso contrario, verrà restituito lo stato predefinito "".

Come impostazione predefinita, restituisce lo stesso stato

Prima di dimenticare, assicurati di includere questo riduttore appena creato nella chiamata della funzione CombinaReduttori.

riduttori / index.js

...
importare digitando da "./typing";

export default combinReducers ({
  utente,
  messaggi,
  digitando,
  contatti,
  activeUserId
});

Assicurarsi di ispezionare i registri e confermare che un campo di battitura è effettivamente collegato all'oggetto stato.

Va bene. Ora creiamo il componente MessageInput effettivo. Poiché questo componente parlerà direttamente con l'archivio Redux per l'impostazione e ottenere il suo valore di battitura, dovrebbe essere creato nella directory containers.

Mentre ci sei, crea anche un file MessageInput.css.

contenitori / MessageInput

import React da "reagire";
importare store da "../store";
importare {setTypingValue} da "../actions";
import "./MessageInput.css";

const MessageInput = ({value}) => {

  const handleChange = e => {
    store.dispatch (setTypingValue (e.target.value));
  };

  ritorno (
    
              ); }; esporta MessageInput predefinito;

Non succede nulla di magico lassù.

Ogni volta che l'utente digita nella casella di input, l'evento onChange viene generato. Questo è il turno attiva la funzione handleChange. handleChange a sua volta invia l'azione setTypingValue che abbiamo creato in precedenza. Questa volta, passando il payload richiesto, e.target.value.

Abbiamo creato il componente, ma per mostrarlo nella finestra di chat dobbiamo includerlo nell'istruzione return di ChatWindow.js:

...
importare MessageInput da "./MessageInput";
const {typing} = stato;

ritorno (
    
                            );

E ora, abbiamo funzionato!

Uh, ma è davvero brutto :(

Rendiamolo bello.

contenitori / MessageInput.css

.Messaggio {
  larghezza: 80%;
  margine: 1rem auto;
}
.Message__input {
  larghezza: 100%;
  imbottitura: 1rem;
  sfondo: rgba (0, 0, 0, 0,8);
  colore: #fff;
  bordo: 0;
  bordo-raggio: 10px;
  dimensione carattere: 1rem;
  contorno: 0;
}

Dovrebbe essere abbastanza per fare la Magia!

I risultati finora!

Stai meglio?

Scommetto che è!

Invio del modulo

Al momento, quando si digita un messaggio e si preme Invio, non viene visualizzato nell'elenco delle conversazioni e la pagina viene ricaricata.

Terribile!

Gestiamo l'invio del modulo.

In MessageInput.js, aggiungi un gestore eventi handleSubmit come mostrato di seguito:

...

...

...

Pensaci per un minuto. Per aggiornare l'elenco dei messaggi nella conversazione ... dobbiamo inviare un'azione!

Questa azione deve prendere il valore nella casella di input e aggiungerlo ai messaggi dell'utente attivo.

Va bene, quindi questa sembra una buona forma per l'azione:

{
  digitare: "SEND_MESSAGE",
  payload: {
    Messaggio,
    ID utente
  }
}

Capito?

Ora scriviamo la funzione handleSubmit:

// prima recupera l'oggetto stato corrente
const state = store.getState ();

const handleSubmit = e => {
    e.preventDefault ();
    const {typing, activeUserId} = stato;
    store.dispatch (sendMessage (digitando, activeUserId));
  };

Ecco cosa succede nella funzione handleSubmit:

Con e.preventDefault (), penso che tu sappia già cosa fa. Il valore di digitazione e activeUserId vengono recuperati dallo stato poiché entrambi verranno utilizzati per creare l'azione inviata.

Infine, l'azione viene inviata con store.dispatch (sendMessage (typing, activeUserId)).

Oops, ma con un creatore di azioni, sendMessage.

In actions / index.js, crea il creatore dell'azione sendMessage:

import {
  ...
  INVIA MESSAGGIO
} da "../constants/action-types";

export const sendMessage = (message, userId) => ({
  tipo: SEND_MESSAGE,
  payload: {
    Messaggio,
    ID utente
  }
})

Ciò significa anche che la costante del tipo di azione SEND_MESSAGE deve essere creata in constants / action-types.js.

export const SEND_MESSAGE = "SEND_MESSAGE";

Prima di testare il codice, non dimenticare di aggiornare le importazioni del creatore dell'azione in MessageInput.js per includere sendMessage.

import {setTypingValue, sendMessage} da "../actions";

Quindi provalo. Il codice funziona?

No, no.

Il modulo viene inviato, la pagina non viene ricaricata a causa dell'invio del modulo, l'azione viene inviata, ma non vengono ancora aggiornati.

Non abbiamo fatto nulla di male, tranne per il fatto che il tipo di azione non è stato soddisfatto in nessuno dei riduttori.

I riduttori non sanno nulla di questa azione di tipo appena creata, SEND_MESSAGE.

Risolviamolo dopo.

Aggiornamento dello stato del messaggio

Ecco un elenco di tutti i riduttori che abbiamo a questo punto:

activeUserId.js
contacts.js
messages.js
typing.js
user.js

Quale di questi pensi che dovrebbe interessare l'aggiornamento dei messaggi in una conversazione utente?

Sì, il riduttore di messaggi.

Ecco l'attuale contenuto del riduttore di messaggi:

import {getMessages} da "../static-data";

esporta messaggi funzione predefiniti (state = getMessages (10), azione) {
  stato di ritorno;
}

Non sta succedendo molto.

Importa il tipo di azione SEND_MESSAGE e cominciamo a gestirlo in questo riduttore di messaggi.

import {getMessages} da "../static-data";
importare {SEND_MESSAGE} da "../constants/action-types";

esporta messaggi funzione predefiniti (state = getMessages (10), azione) {
  switch (action.type) {
    caso SEND_MESSAGE:
      ritorno "";
    predefinito:
      stato di ritorno;
  }
}

Ora stiamo gestendo il tipo di azione, SEND_MESSAGE ma viene restituita una stringa vuota.

Non è quello che vogliamo, ma lo costruiremo da qui. Nel frattempo, quale pensi sia la conseguenza di restituire una stringa vuota qui?

Lascia che ti mostri.

Tutti i messaggi scompaiono! Ma perché? Questo perché non appena si preme invio, viene inviata l'azione SEND_MESSAGE. Non appena questa azione raggiunge il riduttore, il riduttore restituisce una stringa vuota "".

Pertanto, non ci sono messaggi nell'oggetto stato. È tutto finito!

Questo è assolutamente inaccettabile.

Ciò che vogliamo è conservare qualunque messaggio sia nello stato. Tuttavia, vogliamo aggiungere un nuovo messaggio solo ai messaggi dell'utente attivo.

Va bene. Ma come?

Ricorda che ogni utente ha i propri messaggi associati al proprio ID. Tutto quello che dobbiamo fare è scegliere come target questo ID e aggiornare SOLO i messaggi presenti.

Ecco come appare graficamente:

una panoramica di ciò che deve essere fatto.

Dai un'occhiata alla console nel grafico sopra. Il grafico presuppone che un utente abbia inviato il modulo tre volte con il testo, Ciao.

Come previsto per il testo, Hi compare tre volte diverse nelle conversazioni della chat per quel particolare contatto.

Ora dai un'occhiata alla console. Ti darà un'idea di ciò a cui aspiriamo nella soluzione del codice a venire.

In questa applicazione, ogni utente ha 10 messaggi. Ciascuno dei messaggi ha un numero che varia da 0 a 9.

Pertanto, ogni volta che un utente invia un nuovo messaggio, vogliamo aggiungere un nuovo oggetto messaggio ma con un numero crescente!

Nella console nel grafico sopra, noterai che il numero aumenta. 10, 11 e 12.

Inoltre, la forma del messaggio rimane la stessa, con i campi numero, testo e is_user_msg.

{
  numero: 10,
  testo: "il testo digitato",
  is_user_msg: true
}

is_user_msg sarà sempre vero per questi messaggi. Vengono dall'utente!

Ora, rappresentiamo questo con un po 'di codice.

Lo spiegherò bene, perché all'inizio il codice potrebbe sembrare complesso.

Comunque, ecco la rappresentazione all'interno del blocco di commutazione del riduttore di messaggi:

switch (action.type) {
    caso SEND_MESSAGE:
      const {message, userId} = action.payload;
      const allUserMsgs = state [userId];
      const number = + _. keys (allUserMsgs) .pop () + 1;

      ritorno {
        ...stato,
        [ID utente]: {
          ... allUserMsgs,
          [numero]: {
            numero,
            messaggio testuale,
            is_user_msg: true
          }
        }
      };

    predefinito:
      stato di ritorno;
  }

Andiamo oltre questa riga per riga.

Subito dopo il caso SEND_MESSAGE :, conserviamo un riferimento al messaggio e all'utenteId passati dall'azione.

const {message, userId} = action.payload

Per continuare, è anche importante prendere i messaggi dell'utente attivo. Questo viene fatto nella riga successiva con:

const allUserMsgs = state [userId];

Come forse già saprai, lo stato qui non è l'oggetto generale dello stato dell'applicazione. No. È lo stato gestito dal riduttore per il campo dei messaggi.

Poiché il messaggio di ogni contatto è mappato con il proprio ID utente, il codice sopra ottiene i messaggi per l'ID utente specifico passati dall'azione.

Ora, ogni messaggio ha un numero. Questo si comporta come un ID univoco di alcuni tipi. Affinché i messaggi in arrivo abbiano un ID univoco, _.keys (allUserMsgs) restituirà un array di tutte le chiavi dei messaggi dell'utente.

Va bene, lasciami spiegare.

_.keys è come Object.keys (). L'unica differenza qui è che sto usando l'helper di Lodash. Puoi usare Object.keys () se vuoi.

Inoltre, allUserMsgs è un oggetto che contiene tutti i messaggi dell'utente. Sarà simile a questo:

{
0: {
  numero: 0,
  testo: "primo messaggio"
  is_user_msg: false
},
1: {
   numero: 0,
   testo: "primo messaggio"
   is_user_msg: false
 }
}

Questo continuerà fino al decimo messaggio!

Quando eseguiamo _.keys (allUserMsgs) o Object.keys (allUserMsgs), questo restituirà un array di tutte le chiavi. Qualcosa come questo:

[0, 1, 2, 3, 4, 5]

La funzione Array.pop () viene utilizzata per recuperare l'ultimo elemento dell'array. Questo è il numero più grande già esistente per i messaggi del contatto. Un po 'come l'ID del messaggio dell'ultimo contatto.

Una volta recuperato, aggiungiamo + 1 ad esso. Assicurarsi che il nuovo messaggio ottenga + 1 del numero più alto di messaggi disponibili.

Ecco di nuovo tutto il codice responsabile per questo:

const number = + _. keys (allUserMsgs) .pop () + 1;

Se ti stai chiedendo perché c'è un + prima di _.keys (allUserMsgs) .pop () + 1, questo per assicurarti che il risultato sia convertito in un numero anziché in una stringa.

Questo è tutto!

Sulla carne del blocco di codice:

ritorno {
        ...stato,
        [ID utente]: {
          ... allUserMsgs,
          [numero]: {
            numero,
            messaggio testuale,
            is_user_msg: true
          }
        }
      };

Dai un'occhiata da vicino e sono sicuro che ne avrai un senso.

... lo stato farà in modo che non si scherzi con i messaggi precedenti nell'applicazione.

Poiché stiamo utilizzando le notazioni degli oggetti, possiamo facilmente afferrare il messaggio con il particolare ID utente con [userID]

All'interno dell'oggetto, ci assicuriamo che tutti i messaggi dell'utente non vengano toccati: ... allUserMsgs

Infine, aggiungiamo il nuovo oggetto messaggio con il numero precedentemente calcolato!

[numero]: {
  numero,
  messaggio testuale,
  is_user_msg: true
}

Può sembrare complesso, ma non lo è. Spero che tu abbia esperienza con questo tipo di calcoli di stato non mutanti dal tuo sviluppo di React.

Ancora confuso?

Dai di nuovo un'occhiata alla dichiarazione di reso. Questa volta, con alcuni colori di codice. Ciò può aiutare a dare vita al codice:

dai un'occhiata alla dichiarazione di ritorno. Puoi dare un senso a questo adesso?

E questo, amico mio, è la fine dell'aggiornamento della conversazione quando viene inserito un input!

Abbiamo solo alcune modifiche da apportare.

Modifiche per rendere l'esperienza di chat naturale

Ecco come appare lo stato attuale delle cose quando scrivo Ciao! e invia tre volte.

DEMO finora.

Noterai rapidamente due problemi.

  1. Anche se gli input sono stati inviati e i messaggi sono stati correttamente aggiunti alle conversazioni, devo scorrere verso il basso per vedere i messaggi. Non è così che funzionano le app di chat. La finestra della chat dovrebbe scorrere automaticamente verso il basso.
  2. Sarebbe bello cancellare il valore dell'input quando inviato. In questo modo l'utente riceve un feedback immediato sul fatto che il proprio input è stato inviato.

La seconda è una soluzione molto più semplice. Cominciamo da quello.

Stiamo già inviando un'azione SEND_MESSAGE. Possiamo ascoltare questa azione e cancellare il valore di input nel riduttore typing.js.

Facciamo proprio questo.

Aggiungi questo all'interno del blocco switch del riduttore typing.js:

caso SEND_MESSAGE:
      ritorno "";

Il che porta tutto il codice a questo:

riduttore / typing.js

importare {SET_TYPING_VALUE, SEND_MESSAGE} da "../constants/action-types";

digita la funzione predefinita di esportazione (state = "", azione) {
  switch (action.type) {
    caso SET_TYPING_VALUE:
      restituire action.payload;
    caso SEND_MESSAGE:
      ritorno "";
    predefinito:
      stato di ritorno;
  }
}

Ora, una volta che l'azione arriva qui, il valore di battitura verrà cancellato e verrà restituita una stringa vuota.

Ecco quello in azione:

Funziona!

Come previsto, il valore di input è ora cancellato.

Bene, assicuriamoci che la finestra della chat scorra quando viene aggiornata.

Per fare ciò avremo bisogno di un po 'di manipolazione del DOM. Questo è il motivo per cui ho insistito per rendere un componente di classe.

Va bene, parliamo di codice.

Innanzitutto, è necessario creare un riferimento per contenere il nodo DOM delle chat.

costruttore (oggetti di scena) {
    super (oggetti di scena);
    this.chatsRef = React.createRef ();
  }

Se non hai familiarità con React.createRef (), è perfettamente normale. Questo perché React 16 ha introdotto un nuovo modo di creare riferimenti.

Manteniamo un riferimento a questo riferimento tramite this.chatsRef.

Nel DOM renderizzato, aggiorniamo quindi il ref in questo modo:

Ora abbiamo un riferimento al div che contiene tutte le conversazioni della chat.

Assicuriamoci che questo sia sempre fatto scorrere verso il basso quando viene aggiornato.

Saluta i metodi del ciclo di vita!

componentDidMount () {
    this.scrollToBottom ();
  }
  componentDidUpdate () {
    this.scrollToBottom ();
  }

Quindi, non appena il componente viene montato, invochiamo una funzione scrollToBottom. Facciamo lo stesso anche quando l'app si aggiorna!

Ora, ecco la funzione scrollToBottom:

scrollToBottom = () => {
    this.chatsRef.current.scrollTop = this.chatsRef.current.scrollHeight;
  };

Tutto ciò che stiamo facendo è aggiornare la proprietà scrollTop in modo che corrisponda a scrollHeight

Non così difficile This.chatsRef.current si riferisce al nodo DOM in questione.

Ecco tutto il codice per Chats.js a questo punto.

...

class Chats estende Component {
  costruttore (oggetti di scena) {
    super (oggetti di scena);
    this.chatsRef = React.createRef ();
  }
  componentDidMount () {
    this.scrollToBottom ();
  }
  componentDidUpdate () {
    this.scrollToBottom ();
  }
  scrollToBottom = () => {
    this.chatsRef.current.scrollTop = this.chatsRef.current.scrollHeight;
  };

  render () {
    ritorno (
      
        {this.props.messages.map (message => (                    ))}            );   } } esporta chat predefinite;

Hey! Con ciò abbiamo Skypey che funziona come previsto!

Ecco una demo. Notare come la posizione di scorrimento si aggiorna non appena viene montato il componente e quando viene digitato un messaggio, anche il componente si aggiorna.

I risultati finali sembrano tutti buoni! Bellissima!

Roba impressionante!

Quindi eccitato!

Siamo arrivati ​​così lontano :)

Conclusione e sintesi

Oh mio! Questa è stata un'esperienza fantastica per me. Costruire Skypey è stato molto divertente.

Lo amavi? Mi piacerebbe vedere la tua versione di Skypey. Cambia i colori, modifica il design e costruisci qualcosa di meglio!

Quando hai finito, mandami un tweet e sarò felice di tirarti su di morale.

Ecco un riepilogo di alcune delle cose che abbiamo imparato finora:

  • È buona norma pianificare sempre il processo di sviluppo dell'applicazione prima di passare al codice.
  • Nell'oggetto stato, evitare le entità nidificate a tutti i costi. Mantieni l'oggetto stato normalizzato.
  • La memorizzazione dei campi di stato come oggetti presenta alcuni vantaggi. Siate ugualmente consapevoli dei problemi con l'uso di oggetti, principalmente la mancanza di ordine.
  • La libreria dell'utilità lodash è molto utile se si sceglie di utilizzare oggetti su array all'interno dell'oggetto state.
  • Non importa quanto poco, prenditi sempre del tempo per progettare l'oggetto stato della tua applicazione.
  • Con Redux, non devi sempre tramandare oggetti di scena. È possibile accedere ai valori dello stato direttamente dal negozio.
  • Mantieni sempre una struttura di cartelle ordinata nelle tue app Redux, come avere tutti i principali attori Redux nelle proprie cartelle. A parte la struttura del codice generale ordinata, questo rende più facile per le altre persone collaborare al tuo progetto in quanto sono probabilmente conversanti con la stessa struttura di cartelle.
  • La composizione del riduttore è davvero eccezionale, specialmente quando la tua app cresce. Ciò aumenta la testabilità e riduce la tendenza a errori difficili da rintracciare.
  • Per la composizione del riduttore, utilizzare CombinaReduttori dalla libreria redux.
  • L'oggetto passato nella funzione combinReduttori è progettato per assomigliare allo stato dell'applicazione, con ogni valore ottenuto dai riduttori associati.
  • Rompere sempre i componenti più grandi in bit più piccoli gestibili. È molto più facile farsi strada in quel modo.

Ci vediamo dopo!

esercizi

L'app Skypey che abbiamo creato qui non è tutto quello che c'è da sapere sull'app. Ci sono altre due attività per te.

  • Estendi l'app Skypey che abbiamo creato per gestire la modifica del messaggio di un utente come mostrato di seguito.
Includi la funzionalità di modifica dei messaggi mostrata qui.
  • Estendi l'app Skypey che abbiamo creato per gestire anche l'eliminazione del messaggio di un utente. Proprio come mostrato di seguito.
Includi la funzionalità di eliminazione dei messaggi mostrata qui.

Dovrebbero essere divertenti da implementare!

Capitolo 5: What Next?

Il seguito di questo libro attuale

Il libro che stai leggendo è uno su tre nel sequel Redux Trio.

Nel secondo libro, Understanding Redux 2, spiego dettagliatamente i concetti avanzati di Redux avanzati come Middlewares, componenti di ordine superiore, effettuare chiamate Ajax e altro ancora.

Non finisce qui.

Ti mostrerò anche alcune delle librerie Redux della community più amate per risolvere i problemi più comuni. Riseleziona, Redux-form, Redux-thunk, Ricomponi e molti altri.

La sezione seguente è un estratto di, Comprensione di Redux 2.

Presentazione di React-Redux
Andare in banca ogni volta che è necessario effettuare un prelievo dal proprio conto è un tale dolore. Bene, non sudare. Questo è il 2018. Abbiamo internet banking, giusto?
Torna a Redux.
Configurare il Riduttore, iscriversi allo Store, ascoltare e ripetere il rendering in caso di cambiamenti di stato ... possiamo ridurre alcune delle seccature.
Come l'Internet banking porta una ventata di aria fresca nel processo di prelievo di denaro dal tuo conto, "vincoli" come React-redux rendono anche leggermente più semplice utilizzare Redux con React - senza problemi di prestazioni.

Che dolce.

Pronto?

Lo tratterò in profondità nel libro di follow-up, Comprensione di Redux 2.

E molto altro!

Fino ad allora, ti prenderò più tardi!

Ehi, continua a scrivere codice!

Tanto amore