La definizione ufficiale recita che Redux è "un contenitore di stati prevedibili per le applicazioni JavaScript". Al posto del termine "prevedibile" avremmo potuto usare "deterministico", ma forse questo non rende più chiara la definizione a chi parte da zero. Il fatto è che Redux nasce per risolvere un problema a cui siamo talmente abituati nello sviluppo delle nostre applicazioni che quasi non ci facciamo più caso: la gestione dello stato.
Lo stato dell'applicazione
Da un punto di vista puramente tecnico, lo stato di un sistema (per esempio di un'applicazione) è l'insieme delle condizioni interne in uno specifico istante che determinano il risultato delle interazioni con l'esterno.
In altre parole, lo stato di un'applicazione è l'insieme delle informazioni che determinano l'output in corrispondenza di un dato input in uno specifico istante.
Ad esempio, che un sistema di prenotazione online mostri un giorno una disponibilità per quattro persone e l'indomani la stessa operazione fornisca disponibilità solo per due persone dipende dal fatto che nel frattempo è cambiato lo stato interno del sistema.
Nello sviluppo di applicazioni abbiamo sempre a che fare con la gestione dello stato e proprio questa gestione è uno dei punti più critici responsabili di buona parte della complessità del software.
I dati e stato nelle single page applications
Prendiamo l'architettura di applicazioni JavaScript più usata al momento: la Single Page Application. Questo tipo di applicazione si trova a dover gestire:
- dati provenienti dal server e memorizzati in una cache locale;
- dati generati dall'applicazione stessa che devono essere inviati al server;
- dati che devono rimanere in locale per rappresentare ad esempio la situazione corrente dell'interfaccia utente o le preferenze espresse dall'utente.
La gestione di questi dati è complessa non tanto per la mole di dati in sé, ma per il fatto che questi dati possono variare per cause diverse: interazioni con l'utente, interazioni con il server, modifiche scatenate da altre modifiche e così via.
Se poi aggiungiamo il fatto che queste modifiche possono avvenire in maniera asincrona, lo scenario può risultare abbastanza complesso ed è facile perdere il controllo sull'evoluzione dello stato, con il risultato che, in presenza di malfunzionamenti, è davvero difficile ricreare una situazione tale da consentire un'analisi del problema e della sua risoluzione.
Uno degli approcci tradizionali alla gestione dello stato di un'applicazione è la separazione delle competenze. Model-View-Controller, Model-View-ViewModel e altri pattern analoghi provano a separare lo stato corrente (il modello) dalle funzionalità che lo modificano e lo presentano all'utente, ma non risolvono del tutto il problema, dal momento che le modifiche al modello possono avvenire in maniera incontrollata da più parti.
Redux, i tre principi fonamentali
Dan Abramov, il creatore di Redux, individua la causa della complessità nella gestione dello stato di un'applicazione in due elementi: modificabilità e asincronicità. La soluzione proposta da Redux mira proprio a rimuovere questi due aspetti, come vedremo nel corso di questa guida, basandosi su tre principi fondamentali:
- esiste una singola fonte di verità
lo stato dell'intera applicazione è memorizzato in un unico oggetto - lo stato è in sola lettura
non è possibile modificare direttamente le informazioni memorizzate nello stato dell'applicazione; l'unico modo per farlo è tramite azioni esplicite - le modifiche allo stato vanno fatte con funzioni pure
lo stato corrente viene sostituito da un nuovo stato generato da funzioni esclusivamente in base ad una azione ed allo stato precedente
L'applicazione di questi principi definisce l'architettura di Redux e i vincoli che deve rispettare un'applicazione che intende gestire lo stato secondo questo approccio.
Redux, i benefici
Il rispetto di questi principi consente di ottenere dei benefici come ad esempio:
- la predicibilità delle transizioni di stato
le variazioni dello stato di un'applicazione risultano analizzabili e riproducibili, ed è pertanto più semplice comprendere come si è arrivati ad una determinata situazione - l'organizzazione del codice
come vedremo, Redux costringe ad organizzare il codice seguendo uno specifico pattern, il che definisce indirettamente uno standard di codifica all'interno di un team di sviluppo - il server rendering
è possibile definire uno stato iniziale a piacere e far partire l'applicazione da quello stato, in questo modo possiamo agevolare il rendering di applicazioni JavaScript a partire dal server - il tracciamento dello stato
è possibile tenere traccia delle transizioni di stato sia a scopo di debugging che per implementare azioni come l'undo ed il redo di determinate azioni - la testabilità del codice
dal momento che le modifiche allo stato sono effettuate tramite funzioni pure, risulta più semplice scrivere unit test per le applicazioni
... e i limiti
Prima di proseguire però è opportuno dire che Redux non è la soluzione a tutti i mali. I benefici che introduce nella gestione dello stato di un'applicazione hanno un prezzo in termini di complessità, o meglio, di indirezione nella scrittura del codice. Come lo stesso Abramov suggerisce, non è detto che la nostra applicazione debba utilizzare Redux. Introdurre Redux nell'architettura di un'applicazione JavaScript va fatto con cognizione di causa, valutando opportunamente vantaggi e svantaggi derivanti.
Architettura generale
Prima di addentrarci nell'utilizzo della libreria e nella scrittura di codice, diamo un'occhiata al pattern architetturale proposto da Redux ed agli attori coinvolti. La seguente figura riassume sinteticamente l'architettura di base del modello Redux:
Nel diagramma possiamo individuare i seguenti attori:
Elemento | Descrizione |
---|---|
State | Rappresenta lo stato dell'applicazione ed è costituito da un oggetto con struttura arbitraria, dipendente dalle nostre esigenze applicative; in linea di massima lo stato dell'applicazione ha una struttura ad albero, ma questo non è assolutamente richiesto da Redux |
Store | È il contenitore di Redux che custodisce lo stato dell'applicazione e ne consente l'accesso e la manipolazione dall'esterno; come esiste un unico oggetto che rappresenta lo stato dell'applicazione, così esiste un unico store per la sua gestione |
Reducer | Un reducer è una normale funzione JavaScript che prende lo stato corrente e un'azione e restituisce lo stato successivo; esso è responsabile della transizione tra uno stato e l'altro nel flusso operativo della nostra applicazione ed è fondamentale che sia una funzione pura, cioè una funzione senza effetti collaterali, che dato uno specifico input restituisce sempre lo stesso output |
Action (Azione) | È un oggetto JavaScript che rappresenta un'azione in grado di cambiare lo stato corrente, cioè di sostituire l'attuale stato con un nuovo stato; Redux non impone alcun vincolo sulla struttura della Action, anche se per convenzione in genere essa ha almeno una proprietà type che identifica il tipo di azione; sempre per convenzione, una Action può avere una proprietà payload che contiene eventuali parametri che caratterizzano una specifica azione: avremo modo di chiarire questi concetti più avanti |
Action creator | In base alla complessità di un'applicazione può essere utile definire una funzione che genera un'azione in base a determinati parametri: questa funzione è detta Action creator |
Dispatcher | È il componente che ha il compito di inviare una Action allo Store il quale, tramite un opportuno Reducer, effettua la transizione di stato prevista dal tipo di Action |
L'architettura di Redux ha quindi il compito di consentire la gestione dello stato di un'applicazione mediante passaggi chiari e ben definiti.
In particolare, le possibili modifiche, o meglio, le possibili transizioni di stato applicabili sono determinate dalle Action previste. Pertanto il flusso generale per modificare lo stato corrente di un'applicazione segue i seguenti passi:
- viene individuata una Action, eventualmente generata tramite un Action creator
- tale Action viene inviata allo Store tramite un Dispatcher
- all'interno dello Store, un Reducer sostituisce allo stato corrente l'eventuale nuovo stato individuato in base all'analisi della Action
Link utili