Tempo di lettura: 11 minuti
Redux è una libreria Javascipt atta a gestire lo stato dei componenti. In molti associano Redux e React ma in realtà essa è indipendente e può essere utilizzata semplicemente con Javascript o con altri framework tipo Angular.
La comprensione della stessa non è delle più immediate , quindi ho deciso di scrivere una guida pratica con esempi semplici e di facile apprendimento (almeno lo spero 🙂 ).
Redux e React: Terminologie di base Javascript
Per poter seguire la guida è necessario almeno avere le basi di ES6 e React, in quanto connetteremo Redux a quest’ultimo. Se siete all’asciutto vi consiglio la seguente guide : REACT E REDUX: LA GUIDA COMPLETA dove all’interno troverete una ricca sezione anche di ES6.
Inoltre dovete avere familiarità con alcune terminologie elencate di seguito:
Funzione pura
Si tratta di una normalissima funzione ma che deve soddisfare due vincoli:
- Non deve produrre effetti collaterali.
- dato un insieme di input questa deve sempre restituire lo stesso output.
Ad esempio eccone una che restituisce la somma di due numeri:
/* Pure add function */ const add = (x,y) => { return x+y; } console.log(add(1,2)) //3
La funzione non è pura quando esegue qualche cosa di diverso del calcolare il suo valore di ritorno, ad esempio:
const y = 20; const impureAdd = (x) => { console.log(`The inputs are ${x} and ${y}`); return x+y; }
questa esegue la somma di x e della costante y inoltre ne registra il risultato nella console il che è considerato un effetto collaterale.
Effetti collaterali osservabili
Effetti collaterali osservabili è un termine utilizzato nell’interazione delle funzioni con dei dati esterni, ad esempio attraverso la comunicazione con un server e quindi con un END POINT che restituisce dei dati o a cui si possono inviare dati:
- fare chiamate API
- registrare nella console o stampare dati
- convertire i dati
- manipolare il DOM
- recuperare l’ora corrente
Componenti contenitore e di presentazione
Quando si lavora con React è una best practice dividere i componenti in due categorie : di presentazione e contenitore (containers). Conosciuti anche come Dumb e Smart.
I primi si occupano della vista mentre i secondi della parte comportamentale dello stesso.
Oggetti immutabili ed oggetti mutabili
Lo stato di un oggetto Immutabile non può essere modificato dopo la creazione dello stesso mentre questo può variare negli oggetti Mutabili.
Il seguente esempio dimostra chiaramente la differenza:
/*Strings and numbers are immutable */ let a = 10; let b = a; b = 3; console.log(`a = ${a} and b = ${b} `); //a = 10 and b = 3 /* But objects and arrays are not */ /*Let's start with objects */ let user = { name: "Bob", age: 22, job: "None" } active_user = user; active_user.name = "Tim"; //Both the objects have the same value console.log(active_user); // {"name":"Tim","age":22,"job":"None"} console.log(user); // {"name":"Tim","age":22,"job":"None"} /* Now for arrays */ let usersId = [1,2,3,4,5] let usersIdDup = usersId; usersIdDup.pop(); console.log(usersIdDup); //[1,2,3,4] console.log(usersId); //[1,2,3,4]
ricordatevi che lo stato di un oggetto Immutabile non deve essere modificato dopo che è stato realizzato. In javascript stringhe e numeri non sono modificabili al contrario di oggetti ed array i quali lo sono.
Quindi per rendere array ed oggetti immutabili dovete utilizzare sempre il metodo Object.Assign() di javascript o lo spread operator introdotto da ES6 ( [… array,..]). Questi metodi non mutano gli oggetti esistenti ma creano copie degli stessi, se volete saperne di più : Object.Assign() , spread operator.
Ecco un esempio:
let user = { name: "Bob", age: 22, job: "None" } active_user = Object.assign({}, user, {name:"Tim"}) console.log(user); //{"name":"Bob","age":22,"job":"None"} console.log(active_user); //{"name":"Tim","age":22,"job":"None"}
Readux e React: Cosa è Redux ?
La pagina ufficiale di Redux lo definisce come tale:
REDUX E’ UN CONTENITORE DI STATO PREVEDIBILE PER APPLICAZIONI JAVASCRIPT
Come detto in precedenza questa è una libreria js indipendente e può essere associata ad altri framework tipo Angular o,addirittura semplicemente, con javascript mediante vanilla js.
Tuttavia Redux e React combaciano alla perfezione vista la natura funzionale di quest’ultimo. Ecco un esempio:
Come potete notare l’applicazione ha uno stato iniziale, una qualsiasi interazione da parte degli utenti innesca un’azione la quale modifica lo stato. Una volta aggiornato viene visualizzata la pagina. In parole povere lo stato di un componente determina ciò che viene eseguito.
Ogni componente , in React, ha un suo stato locale accessibile dall’interno del componente stesso. Potete passare lo stato ai componenti figli mediante le props.
Solitamente lo stato si utlizza per memorizzare :
- Lo stato dell’applicazione come , ad esempio, i dati recuperati da un server, lo stato di accesso di un utente, ecc..
- 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 di un modulo in un componente controllato.
Tuttavia la memorizzazione dello stato dei dati dell’applicazione in un componente può andar bene quando si dispone di una semplice applicazione , quindi con pochi componenti:
Man mano che l’applicazione cresce, quindi ha tanti componenti, diversi stati da gestire, aumentano il numero dei livelli nella gerarchia degli stessi, converrete con me che la gestione diventa problematica:
ecco in questo caso entra in scena Redux 🙂 .
Perchè utilizzare Redux
React è una libreria di vista quindi non è suo compito gestire specificamente lo stato di un’applicazione. Parliamo del principio della separazione delle competenze.
Redux ci consente di separare lo stato dell’applicazione da React. Questa crea un archivio globale che si trova ad un livello superiore dell’applicazione ed alimenta lo stato di tutti gli altri componenti.
L’intero stato dell’applicazione si trova all’interno dell’oggetto store. I componenti si aggiornano ogni volta che questo viene aggiornato con un minimo impatto sulle prestazioni. In parole povere potete trattare tutti i componenti di React come Dumb (components di presentazione) e quelli di Redux come Smart (containers).
Architettura di Redux
Nello studio di Redux bisogna abituarsi a dei concetti essenziali, l’immagine sotto ne descrive l’architettura:
via via vedremo come funziona , quindi passiamo alla parte pratica. Abbineremo Redux a React per cui createvi una cartella, rinominatela come vi pare. Vi consiglio di utilizzare l’ide Visual studio Code , apritelo , quindi aprite la cartella appena creata, fate click su ctrl+ò vi si aprirà il terminale.
Una volta sul terminale scaricatevi React (vi ricordo che se non conoscete React potete seguire il corso REACT E REDUX: LA GUIDA COMPLETA) mediante il comando:
npx create-react-app shop-list
Redux e React: Lo Store
Questo è un oggetto Javascript ricco di coppie chiave-valore e rappresenta lo stato attuale dell’applicazione. A differenza degli oggetti di stato di React i quali vengono distribuiti attraverso svariati componenti, si ha un solo Store. Questo favorisce lo stato dell’applicazione , ogni volta che lo stesso si aggiorna, la vista è rivisualizzata.
Non si può mutare o modificare lo Store ma si possono creare nuove versioni dello stesso.
(previousState, action) => newState
Lo store ha tre metodi i quali servono per comunicare con il resto dell’architettura:
- Store.getState() -> accedere allo stato corrente dell’applicazione
- Store.dispatch(action) -> innesca un cambiamento dello stato in base all’azione inviata
- Store.subscribe(listener) ->ascolta qualsiasi cambiamento nello stato. Viene chiamato ogni volta che viene inviata un’azione nello Store
Ora aprite il terminale e recatevi all’interno della cartella dell’applicazione appena creata:
lucio@LENOVO MINGW64 /e/react/ReactArticle/redux $ cd shop-list/
quindi , sempre da terminale installate Redux:
lucio@LENOVO MINGW64 /e/react/ReactArticle/redux/shop-list (master) $ npm i redux
src/index.js
Aprite il file src/index.js e , visto che al momento lavoreremo solo con Redux, cancellatene l’intero contenuto poi digitate:
import { createStore } from "redux"; // This is the reducer const reducer = () => { /*Something goes here */ } //initialState is optional. //For this demo, I am using a counter, but usually state is an object const initialState = 0 const store = createStore(reducer, initialState);
Ascoltiamo eventuali modifiche nello store , quindi con console.log() lo stato corrente nello stesso.
store.subscribe(()=>{ console.log("State has changed" + store.getState()) })
Si ma come aggiorniamo lo store 😕 ? Ma con le Azioni (actions) è chiaro 🙂 .
Action Creators
Le Action sono dei semplici oggetti Javascript le quali inviano informazioni dalla vostra applicazione allo Store. Supponiamo di realizzare un semplice contatore e di incrementarlo mediante un pulsante. Una volta premuto si attiverà un’azione che verrà inviata allo Store:
{ type: "INCREMENT", payload: 1 }
sono l’unica fonte di informazione dello store.Lo stato di quest’ultimo si modifica solo in risposta ad un’azione.
E’ obbligatorio che ogni azione abbia una proprietà type: la quale descrive ciò che intende fare l’oggetto dell’azione. A parte ciò la struttura dell’azione dipende completamente da noi. Dato che questa rappresenta la quantità minima di informazioni atte a trasformare lo stato dell’applicazione, è importante che non sia carica di info.
Nell’esempio precedente la proprietà type è impostata su INCREMENT, inoltre vi è un’altra proprietà payload, questa non è obbligatoria come il type difatti potreste rinominarla o, addirittura ometterla.
Potreste inviare un’action allo Store come la seguente:
store.dispatch({type: "INCREMENT", payload: 1});
In Redux comunque non si utilizzano le Action direttamente bensì chiameremo le funzioni che le restituiscono, queste sono conosciute come creatori di azioni (Action creators):
const incrementCount = (count) => { return { type: "INCREMENT", payload: count } }
quindi per aggiornare lo stato del contatore è necessario spedire la Action incrementCount così:
store.dispatch(incrementCount(1)); store.dispatch(incrementCount(1)); store.dispatch(incrementCount(1));
codice completo di src/index.js:
import { createStore } from "redux"; // This is the reducer const reducer = () => { /*Something goes here */ } //initialState is optional. //For this demo, I am using a counter, but usually state is an object const initialState = 0 const store = createStore(reducer, initialState); store.subscribe(() => { console.log("State has changed " + store.getState()) }) const incrementCount = (count) => { return { type: "INCREMENT", payload: count ++ } } store.dispatch(incrementCount(1)); store.dispatch(incrementCount(1)); store.dispatch(incrementCount(1));
ora da console digitate npm start per avviare il server ed aprite il browser all’indirizzo localhost:3000 ,avviate la console con il comando f12 ed otterrete :
in quanto non ci resta che definire il riduttore Reducer , cioè un meccanismo che converta le informazioni fornite dall’azione e trasformi lo stato delle Store.
Reducers
La documentazione Recita :
DATI GLI STESSI ARGOMENTI UN REDUCER DOVREBBE CALCOLARE LO STATO E RESTITUIRLO. SENZA SORPRESE. SENZA EFFETTI COLLATERALI. NESSUNA CHIAMATA ALLE API. NESSUNA MUTAZIONE. SOLO UN CALCOLO.
Un’azione descrive il problema ed il Reducer lo risolve. Nell’esempio precedente ,il metodo incrementCount ha restituito un’azione che ha fornito informazioni sulla tipologia di cambiamento da fare nello stato.. Il Reducer utilizza tali informazioni per aggiornare lo stato.
Il Reducer deve sempre essere una funzione pura quindi dato un input deve sempre restituire lo stesso output. Non va utilizzato,quindi,per chiamate API o per effetti collaterali come chiamate AJAX.
Detto questo recuperiamo il Reducer per il nostro contatore:
// This is the reducer const reducer = (state = initialState, action) => { switch (action.type) { case "INCREMENT": return state + action.payload default: return state } }
questo accetta due argomenti – stato ed azione – e restituisce un nuovo stato:
(previousState, action) => newState
Lo stato accetta un valore predefinito , initalState il quale viene utilizzato solo quando il valore dello stesso è indefinito (undefined). In caso contrario, il valore effettivo dello stato verrà trattenuto. Quindi utiliziamo l’istruzione switch per selezionare l’azione necessaria.
Provate ad avviare il server per verificare se tutto funziona come previsto:
Quindi aggiungiamo un case per DECREMENT :
const reducer = (state = initialState, action) => { switch (action.type) { case "INCREMENT": return state + action.payload case "DECREMENT": return state - action.payload default: return state } }
Action creator:
const decrementCount = (count) => { return { type: "DECREMENT", payload: count } }
infine inviatelo allo store:
store.dispatch(incrementCount(6)); store.dispatch(decrementCount(3));
Questo per il momento è tutto. Nel prossimo articolo applicheremo i concetti studiati per realizzare un’applicazione React con Redux.
Ciauz 🙂