Spesso Redux viene associato a React, la nota libreria di Facebook per la creazione di interfacce utente. Molti sviluppatori pensano che questo connubio sia indissolubile, ovvero che se usi React devi necessariamente usare Redux (e viceversa). In realtà le due librerie sono totalmente scollegate: è possibile usare React senza Redux, come è pure possibile usare Redux con JavaScript puro (come abbiamo fatto finora in questa guida) o con altre librerie e framework come Angular o jQuery.
Il motivo per cui spesso si tende ad accoppiare Redux con React dipende probabilmente dalla natura dei componenti di React: elementi il cui rendering visivo deriva dalle variazioni di stato.
Questo reagire alle variazioni di stato si presta bene ad essere interfacciato con il modello publisher/subscriber proposto da Redux, come abbiamo visto quando abbiamo illustrato l'integrazione con l'interfaccia grafica della nostra applicazione. In altre parole, possiamo registrare un componente React tramite store.subscribe()
in modo che venga avvertito quando si verifica un cambio di stato in Redux per effettuare il rendering automatico sulla base della nuova situazione. Naturalmente non c'è nulla di particolarmente complesso in questo, che potrebbe quindi essere un approccio praticabile. Ma se vogliamo sfruttare al meglio le prestazioni di React dovremmo effettuare alcune analisi delle condizioni dello stato attuale dello store di Redux prima di avviare il rendering. Si pensi, ad esempio, al caso in cui lo stato di Redux ha subito una modifica, ma nessuno dei dati che interessano il nostro componente è stato cambiato. In questa situazione rischieremmo di fare dei rendering inutili che possono avere un impatto non indifferente sulla nostra applicazione.
Per evitare di fare manualmente queste analisi possiamo ricorrere a React Redux, la libreria ufficiale per il binding ottimizzato tra componenti React e lo store di Redux. Per installarla in un ambiente di sviluppo basato su Node.js è sufficiente lanciare il comando:
npm install --save react-redux
Una volta installata la libreria, la prima cosa da fare è rendere disponibile lo store di Redux ai componenti dell'applicazione React. Possiamo farlo intervenendo sul componente di più alto livello nella gerarchia dei componenti dell'applicazione, quello cioè che rappresenta la root della nostra applicazione. Il seguente codice è mostra gli interventi da effettuare:
import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import todoApp from './reducers'
import App from './components/App'
let store = createStore(todoApp)
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
Come possiamo vedere dall'esempio, abbiamo importato la funzione createStore()
dal modulo di Redux ed il componente Provider
dal modulo react-redux. Il ruolo di questo
componente React è unicamente di mettere a disposizione dei propri figli lo store creato tramite createStore()
. Per questo motivo occorre impostare Provider
come componente root della nostra applicazione.
Dopo aver reso disponibile lo store di Redux alla nostra applicazione React, possiamo connettere i componenti ad esso utilizzando la funzione connect()
della libreria react-redux. Il seguente codice è un esempio di utilizzo della funzione:
import { connect } from 'react-redux'
class UserComponent extends Component {
...
}
export default connect(
(state) => {
return {
userName: state.login.userName,
fullName: state.login.fullName
}
}, (dispatch) => {
return {
saveUser: function(userData) {
....
dispatch(saveActionCreator(userData))
}
}
})(UserComponent)
Nell'esempio vediamo l'importazione della funzione connect()
ed il suo utilizzo come decorator del nostro componente UserComponent
. La funzione prevede due parametri ed il risultato finale dell'applicazione sul nostro componente è il componente stesso arricchito di props che consentono indirettamente di interagire con lo store di Redux.
I due parametri da passare a connect()
sono due funzioni, rappresentate nell'esempio sotto forma di arrow function.
La prima funzione riceve lo stato corrente di Redux e restituisce un oggetto sulle cui proprietà sono mappati i valori dello stato rilevanti per il componente corrente.
La seconda funzione riceve come parametro la funzione dispatch()
di Redux e restituisce un oggetto con dei metodi che interagiscono con lo stato di Redux.
In sintesi, la prima funzione consente di ricevere la porzione di stato a cui il nostro componente è interessato, la seconda funzione consente invece di apportare modifiche allo stato tramite il dispatching di un'azione.
Nel nostro esempio, la prima funzione mappa il nome utente e il nome completo sulle proprietà userName e fullName, mentre la seconda funzione definisce il metodo saveUser()
che effettua il dispatch dell'azione generata dall'action creator saveActionCreator()
.
Come risultato di questi mapping avremo che il componente UserComponent
avrà tra le sue props userName, fullName e saveUser(), potendo quindi effettuare il rendering di variazioni di stato e di richiedere variazioni dello stato di Redux.
Sebbene il collegamento tra componenti React e lo store di Redux sia fattibile in maniera relativamente semplice, occorre evitare di connettere ogni componente della nostra applicazione allo stato di Redux. Uno dei principali motivi riguarda l'efficienza del rendering. Per sfruttare al massimo le caratteristiche dei componenti di React e le funzionalità di Redux occorre effettuare un'attenta analisi dei tipi di componenti che costituiscono la nostra applicazione. In particolare dobbiamo distinguere componenti presentazionali e componenti contenitori. In estrema sintesi, i primi sono quei componenti il cui compito è di presentare dei dati e di consentire all'utente di interagire con essi. I secondi sono quei componenti il cui compito è di contenere un aggregato di componenti presentazionali: spesso rappresentano la root di una gerarchia di componenti e normalmente non dovrebbero avere un riscontro grafico.
Soltanto i componenti contenitori dovrebbero essere connessi a Redux, mentre gli altri componenti dovrebbero ricevere le variazioni di stato tramite le props propagate da un componente all'altro.
Questo consente di ottenere il massimo sia di Redux che di React.
Conclusioni
Con questa lezione si conclude la guida a Redux, nel corso della quale abbiamo esplorato un approccio per la gestione dello stato di un'applicazione che a prima vista potrebbe sembrare un po' complesso e farraginoso, ma offre in cambio predicibilità e rigore nella definizione dell'architettura della nostra applicazione Web.