() translation by (you can also view the original English article)
Quando state imparando React, quasi sempre sentirete persone dire quanto è fantastico Redux e che dovreste fare un tentativo. L'ecosistema sta crescendo a un ritmo rapido e ci sono così tante librerie che si possono collegare con React, come flow, redux, middlewares, mobx, ecc.
Imparare React è facile, ma abituarsi all'intero ecosistema di React richiede tempo. Questo tutorial è un'introduzione a uno dei componenti integranti dell'ecosistema React — Redux.
Terminologia di base Non-Redux
Ecco alcune delle terminologie comunemente usate con le quali non potete non avere familiarità, ma che non sono specifici di Redux in sé. Potete scorrere questa sezione e ritornare quando/se qualcosa non ha senso.
Funzione pura
Una funzione pura è solo una normale funzione con due ulteriori vincoli che deve soddisfare:
- Dato un insieme di input, la funzione deve sempre restituire lo stesso output.
- Non produce effetti collaterali.
Per esempio, ecco una funzione pura che restituisce la somma di due numeri.
1 |
/* Pure add function */
|
2 |
const add = (x,y) => { |
3 |
return x+y; |
4 |
}
|
5 |
|
6 |
console.log(add(2,3)) //5 |
7 |
Le funzioni pure danno un output stimabile e sono deterministiche. Una funzione diventa impura quando esegue qualcosa di diverso dal calcolare il suo valore di ritorno.
Per esempio, la funzione aggiungi qui sotto utilizza uno stato globale per calcolare l'output. Inoltre, la funzione registra anche il valore nella console, che è considerato un effetto collaterale.
1 |
const y = 10; |
2 |
|
3 |
const impureAdd = (x) => { |
4 |
console.log(`The inputs are ${x} and ${y}`); |
5 |
return x+y; |
6 |
}
|
Effetti collaterali osservabili
"Effetti collaterali osservabili" è un termine di fantasia per interazioni avvenute su una funzione con il mondo esterno. Se una funzione tenta di scrivere un valore in una variabile che esiste al di fuori della funzione o tenta di chiamare un metodo esterno, allora si possono chiamare queste cose effetti collaterali.
Tuttavia, se una funzione pura chiama un'altra funzione pura, allora la funzione può essere trattata come pura. Ecco alcuni degli effetti collaterali comuni:
- fare chiamate alle API
- registrare nella console o stampare i dati
- convertire i dati
- manipolazione del DOM
- recuperare l'ora corrente
Componenti contenitore e di presentazione
Dividere l'architettura dei componenti in due è utile mentre si lavora con le applicazioni React. Potete classificarle ampiamente in due categorie: componenti contenitore e componenti di presentazione. Sono anche popolarmente conosciuti come componenti smart e dumb.
Il componente del contenitore si occupa di come funzionano le cose, mentre i componenti di presentazione si occupano di come appaiono le cose. Per comprendere meglio i concetti, ho trattato ciò in un altro tutorial: Componenti contenitore vs presentazione in React.
Oggetti mutabili vs oggetti immutabili
Un oggetto mutabile può essere definito come segue:
Un oggetto mutabile è un oggetto il cui stato può essere modificato dopo la creazione.
L'immutabilità è l'esatto opposto — un oggetto immutabile è un oggetto il cui stato non può essere modificato dopo che è stato creato. In JavaScript, le stringhe e i numeri sono non modificabili, ma gli oggetti e gli array non lo sono. L'esempio dimostra meglio la differenza.
1 |
/*Strings and numbers are immutable */
|
2 |
|
3 |
let a = 10; |
4 |
|
5 |
let b = a; |
6 |
|
7 |
b = 3; |
8 |
|
9 |
console.log(`a = ${a} and b = ${b} `); //a = 10 and b = 3 |
10 |
|
11 |
/* But objects and arrays are not */
|
12 |
|
13 |
/*Let's start with objects */
|
14 |
|
15 |
let user = { |
16 |
name: "Bob", |
17 |
age: 22, |
18 |
job: "None" |
19 |
}
|
20 |
|
21 |
active_user = user; |
22 |
|
23 |
active_user.name = "Tim"; |
24 |
|
25 |
//Both the objects have the same value
|
26 |
console.log(active_user); // {"name":"Tim","age":22,"job":"None"} |
27 |
|
28 |
console.log(user); // {"name":"Tim","age":22,"job":"None"} |
29 |
|
30 |
/* Now for arrays */
|
31 |
|
32 |
let usersId = [1,2,3,4,5] |
33 |
|
34 |
let usersIdDup = usersId; |
35 |
|
36 |
usersIdDup.pop(); |
37 |
|
38 |
console.log(usersIdDup); //[1,2,3,4] |
39 |
console.log(usersId); //[1,2,3,4] |
Per rendere gli oggetti immutabili, utilizzate il metodo Object.assign
per creare un nuovo metodo o il nuovo operatore spread.
1 |
let user = { |
2 |
name: "Bob", |
3 |
age: 22, |
4 |
job: "None" |
5 |
}
|
6 |
|
7 |
active_user = Object.assign({}, user, {name:"Tim"}) |
8 |
|
9 |
console.log(user); //{"name":"Bob","age":22,"job":"None"} |
10 |
console.log(active_user); //{"name":"Tim","age":22,"job":"None"} |
Che cosa è Redux?
La pagina ufficiale definisce Redux come segue:
Redux è un contenitore di stato prevedibile per le applicazioni JavaScript.
Sebbene ciò descriva accuratamente Redux, è facile perdersi quando si vede il quadro generale di Redux per la prima volta. Ha così tanti pezzi mobili che avete bisogno di far combaciare. Ma una volta che lo fate, ve lo prometto, inizierete ad amare Redux.
Redux è una libreria di gestione dello stato che potete collegare con qualsiasi libreria JavaScript, e non solo React. Tuttavia, lavora molto bene con React a causa della natura funzionale di React. Per capirci meglio, diamo un'occhiata allo stato.



Come potete vedere, lo stato di un componente determina ciò che viene eseguito il rendering e come si comporta. L'applicazione dispone di uno stato iniziale, e qualsiasi interazione dell'utente innesca un'azione che aggiorna lo stato. Quando lo stato viene aggiornato, viene visualizzata la pagina.
Con React, ogni componente ha uno stato locale che è accessibile dall'interno del componente, o potete passarli come prop ai componenti figlio. Usiamo solitamente lo stato per memorizzare:
- Lo stato dell'interfaccia utente e i dati transitori. Questo include un elenco di elementi dell'interfaccia utente per il menù di navigazione o gli input del modulo in un componente controllato.
- Lo stato dell'applicazione, ad esempio i dati recuperati da un server, lo stato di accesso dell'utente, ecc.
La memorizzazione dei dati dell'applicazione nello stato di un componente va bene quando si dispone di un'applicazione di base React con pochi componenti.



Tuttavia, la maggior parte delle applicazioni reali avrà molte più caratteristiche e componenti. Quando aumenta il numero dei livelli nella gerarchia del componente, la gestione dello stato diventa problematica.



Perché dovreste usare Redux?
Ecco uno scenario molto probabile che potreste incontrare durante l'utilizzo di React.
- State creando un'applicazione di medie dimensioni, e avete i vostri componenti ordinatamente suddivisi in componenti smart e dumb.
- I componenti smart gestiscono lo stato e poi lo passano ai componenti dumb. Si prendono cura di effettuare le chiamate alle API, recuperare i dati dalla sorgente dei dati, elaborare i dati e quindi impostare lo stato. I componenti dumb ricevono i props e restituiscono la rappresentazione dell'interfaccia utente.
- Quando state per scrivere un nuovo componente, non è sempre chiaro dove posizionare lo stato. Potreste lasciare che lo stato sia parte di un contenitore che è un immediato genitore del componente di presentazione. Meglio ancora, potete spostare lo stato più in alto nella gerarchia di modo che lo stato sia accessibile a componenti di presentazione multipli.
- Quando l'app cresce, vedete che lo stato è sparso ovunque. Quando un componente ha bisogno di accedere allo stato al quale non ha immediatamente accesso, cercate di alzare lo stato fino al componente antenato più vicino.
- Dopo un costante refactoring e pulizia, vi ritrovate con la maggior parte dello stato che conserva i posti nella parte superiore della gerarchia del componente.
- Infine, decidete che è una buona idea lasciare che un componente nella parte superiore gestisca lo stato a livello globale e quindi passi tutto verso il basso. Tutti gli altri componenti possono sottoscrivere i props di cui hanno bisogno e ignorare il resto.
Questo è quello che ho personalmente vissuto con React, e un sacco di altri sviluppatori saranno d'accordo. React è una libreria di vista, e non è compito di React gestire specificamente lo stato. Quello che stiamo cercando è il principio di Separazione delle competenze.
Redux consente di separare lo stato dell'applicazione da React. Redux crea un archivio globale che si trova al livello superiore dell'applicazione e alimenta lo stato di tutti gli altri componenti. A differenza di Flux, Redux non ha oggetti multipli di store. L'intero stato dell'applicazione è all'interno dell'oggetto store, e potenzialmente potreste scambiare il livello di visualizzazione con un'altra libreria con lo store intatto.
I componenti si rieseguono nuovamente ogni volta che lo store è aggiornato, con un impatto minimo sulle prestazioni. Che è una buona notizia, e questo porta insieme con essa tonnellate di benefici. Potete trattare tutti i componenti di React come dumb, e React può concentrarsi solo sul lato vista delle cose.
Ora che sappiamo perché è utile Redux, tuffiamoci nell'architettura di Redux.
L'architettura di Redux
Quando state imparando Redux, ci sono alcuni concetti fondamentali a cui è necessario abituarsi. L'immagine qui sotto descrive l'architettura Redux e come tutto è collegato insieme.



Se siete abituati a Flux, alcuni degli elementi potrebbero sembrarvi familiari. Se no, va bene anche perché percorreremo tutto dalla base. In primo luogo, assicuratevi di che avere installato redux:
1 |
npm install redux
|
Utilizzate create-react-app o la vostra configurazione webpack preferita per impostare il server di sviluppo. Poiché Redux è una gestore di stato indipendente, ancora non collegheremo reagire. Quindi, rimuovete il contenuto di index.js, e armeggeremo con Redux per il resto di questo tutorial.
Store
Lo store è un grande oggetto JavaScript che ha tonnellate di coppie valore-chiave che rappresentano lo stato corrente dell'applicazione. A differenza dell'oggetto di stato di React, che viene cosparso attraverso diversi componenti, abbiamo solo uno store. Lo store fornisce lo stato dell'applicazione, e ogni volta che lo stato si aggiorna, la vista è rivisualizzata.
Tuttavia, non potete mai mutare o modificare lo store. Invece, create nuove versioni dello store.
1 |
(previousState, action) => newState |
Per questo motivo, potete fare un viaggio nel tempo attraverso tutti gli stati, dal momento in cui è stato avviata l'app sul vostro browser.
Lo store ha tre metodi per comunicare con il resto dell'architettura. Sono:
- Store.getState() — per accedere all'albero dello stato corrente della vostra applicazione.
- Store.dispatch(action) — per innescare un cambiamento dello stato basato su un'azione. Ulteriori informazioni sulle azioni qui sotto.
- Store.subscribe(listener) — per ascoltare qualsiasi cambiamento nello stato. Verrà chiamato ogni volta che un'azione viene inviata.
Creiamo uno store. Redux ha un metodo createStore
per creare un nuovo store. Avete bisogno di passargli un reducer, anche se non sappiamo cos'è. Quindi voglio solo creare una funzione denominata reducer. Potete specificare facoltativamente un secondo argomento che imposta lo stato iniziale dello store.
src/index.js
1 |
import { createStore } from "redux"; |
2 |
// This is the reducer
|
3 |
const reducer = () => { |
4 |
/*Something goes here */
|
5 |
}
|
6 |
|
7 |
//initialState is optional.
|
8 |
//For this demo, I am using a counter, but usually state is an object
|
9 |
const initialState = 0 |
10 |
const store = createStore(reducer, initialState); |
Ora ascolteremo eventuali modifiche nello store e poi con console.log()
lo stato corrente dello store.
1 |
store.subscribe( () => { |
2 |
console.log("State has changed" + store.getState()); |
3 |
})
|
Così come aggiorniamo lo store? Redux ha qualcosa chiamato azioni che lo rendono possibile.
Action/Action Creators
Le azioni sono anche semplici oggetti di JavaScript che inviano informazioni dalla vostra applicazione allo store. Se si dispone di un contatore molto semplice con un pulsante di incremento, premendolo risulterà un'azione che viene attivata, che assomiglia a questo:
1 |
{
|
2 |
type: "INCREMENT", |
3 |
payload: 1 |
4 |
}
|
Sono l'unica fonte di informazioni dello store. Lo stato dello store si modifica solo in risposta a un'azione. Ogni azione deve avere una proprietà di tipo che descrive ciò che intende fare l'oggetto dell'azione. A parte questo, la struttura dell'azione dipende completamente da voi. Tuttavia, mantenete la vostra azione piccola perché un'azione rappresenta la quantità minima di informazioni necessarie per trasformare lo stato dell'applicazione.
Per esempio, nell'esempio precedente, la proprietà type è impostata su "INCREMENT", ed è inclusa una ulteriore proprietà di payload. Potreste rinominare la proprietà di payload in qualcosa di più significativo o, nel nostro caso, ometterlo completamente. Potete inviare un'azione allo store come questa.
1 |
store.dispatch({type: "INCREMENT", payload: 1}); |
Durante la scrittura del codice Redux, normalmente non utilizzeremo le azioni direttamente. Invece, saranno chiamate le funzioni che restituiscono le azioni, e queste funzioni sono popolarmente conosciute come creatori di azione. Ecco il creatore di azione per l'azione di incremento che abbiamo discusso in precedenza.
1 |
const incrementCount = (count) => { |
2 |
return { |
3 |
type: "INCREMENT", |
4 |
payload: count |
5 |
}
|
6 |
}
|
Così, per aggiornare lo stato del contatore, è necessario spedire l'azione incrementCount
così:
1 |
store.dispatch(incrementCount(1)); |
2 |
store.dispatch(incrementCount(1)); |
3 |
store.dispatch(incrementCount(1)); |
Se andate sulla console del browser, vedrete che sta funzionando, parzialmente. Otteniamo undefined perché non abbiamo ancora definito il reducer.



Quindi ora abbiamo coperto le azioni e lo store. Tuttavia, abbiamo bisogno di un meccanismo per convertire le informazioni fornite dall'azione e trasformare lo stato dello store. I reducer servono a questo scopo.
Reducers
Un'azione descrive il problema e il reducer è responsabile della soluzione del problema. Nell'esempio precedente, il metodo incrementCount
ha restituito un'azione che ha fornito informazioni sul tipo di cambiamento che abbiamo voluto fare allo stato. Il reducer utilizza queste informazioni per aggiornare effettivamente lo stato. C'è un grande punto evidenziato nella documentazione che dovreste sempre ricordare durante l'utilizzo di Redux:
Dati gli stessi argomenti, un Reducer dovrebbe calcolare lo stato successivo e restituirlo. Senza sorprese. Senza effetti collaterali. Nessuna chiamata alle API. Nessuna mutazione. Solo un calcolo.
Ciò significa che un reducer dovrebbe essere una funzione pura. Dato un insieme di input, deve sempre restituire lo stesso output. Oltre a ciò, non dovrebbe fare più nulla. Inoltre, un reducer non è il posto per gli effetti collaterali come effettuare chiamate AJAX o il recupero dei dati dalle API.
Riempiamo il reducer per il nostro contatore.
1 |
// This is the reducer
|
2 |
|
3 |
const reducer = (state = initialState, action) => { |
4 |
switch (action.type) { |
5 |
case "INCREMENT": |
6 |
return state + action.payload |
7 |
default: |
8 |
return state |
9 |
}
|
10 |
}
|
Il riduttore accetta due argomenti — stato e azione — e restituisce un nuovo stato.
1 |
(previousState, action) => newState |
Lo stato accetta un valore predefinito, initialState
, che verrà utilizzato solo se il valore dello stato è undefined. In caso contrario, verrà trattenuto il valore effettivo dello stato. Usiamo l'istruzione switch per selezionare l'azione giusta. Aggiornate il browser, e tutto funziona come previsto.
Aggiungiamo un case per DECREMENT
, senza il quale il contatore è incompleto.
1 |
// This is the reducer
|
2 |
|
3 |
const reducer = (state = initialState, action) => { |
4 |
switch (action.type) { |
5 |
case "INCREMENT": |
6 |
return state + action.payload |
7 |
case "DECREMENT": |
8 |
return state - action.payload |
9 |
default: |
10 |
return state |
11 |
}
|
12 |
}
|
Ecco il creatore di azione.
1 |
const decrementCount = (count) => { |
2 |
return { |
3 |
type: "DECREMENT", |
4 |
payload: count |
5 |
}
|
6 |
}
|
Infine, inviatelo allo store.
1 |
store.dispatch(incrementCount(4)); //4 |
2 |
store.dispatch(decrementCount(2)); //2 |
Questo è tutto!
Riepilogo
Questo tutorial era destinato a essere un punto di partenza per la gestione dello stato con Redux. Abbiamo coperto tutto l'essenziale necessario per comprendere i concetti fondamentali di Redux come store, action e reducer. Verso la fine del tutorial, abbiamo anche creato una demo di un contatore redux funzionante. Anche se non era molto, abbiamo imparato come tutti i pezzi del puzzle si incastrano.
Negli ultimi due anni, React è cresciuto in popolarità. Infatti, abbiamo un numero di elementi sul marketplace che sono disponibili per l'acquisto, la recensione, l'implementazione e così via. Se state cercando risorse aggiuntive su React, non esitate a dargli un'occhiata.
Nel prossimo tutorial, faremo uso delle cose che abbiamo imparato qui per creare un'applicazione React usando Redux. Restate sintonizzati fino ad allora. Condividete le vostre opinioni nei commenti.