back to top

Introduzione a Flux e Redux

Una delle caratteristiche di React è sicuramente quella che prevede il passaggio di informazioni da un componente padre ai suoi discendenti in un flusso unidirezionale di dati. Abbiamo detto che se immaginiamo la struttura della nosta applicazione come un albero, le informazioni scorrono dal nodo radice alle foglie dell’albero. Man mano che la nostra applicazione cresce abbiamo bisogno di un metodo prevedibile, semplice e affidabile per la gestione dei dati. È sulla base di questo principio che Facebook ha creato Flux.

Cos’è Flux

Flux è un design pattern, uno schema architetturale creato per la gestione dei dati all’interno di applicazioni web. È stato sviluppato insieme a React ma si adatta perfettamente ad essere usato in tutte le applicazioni lato client. Secondo il Team React, il classico pattern MVC non permette di realizzare applicazioni scalabili in cui sia semplice individuare bug e errori durante la fase di sviluppo.

schema semplificato di funzionamento di flux

Grazie a Flux è possibile rimuovere tale complessità. Nelle applicazioni che utilizzano Flux (da ora in poi chiameremo queste applicazioni col semplice nome di applicazioni Flux) possiamo distinguere quattro elementi chiave:

  • Dispatcher che è un oggetto il quale riceve delle Action, degli oggetti Javascript con una spedifica struttura, le elabora e notifica gli Store attraverso le callback che questi hanno precedentemente registrato col Dispatcher. Facebook fornisce un Dispatcher all’interno del package Flux che potete trovare su npm.
  • Store che mantengono lo stato dell’applicazione. Vengono notificati dal Dispatcher in seguito alla ricezione di un’Action attraverso le funzioni callback che hanno precedentemente registrato col Dispatcher. All’interno di queste funzioni, il singolo Store decide come comportarsi per ogni azione che viene lanciata.
  • Action che sono dei semplici oggetti con una proprietà type e altre proprietà aggiuntive opzionali. Vengono lanciate soprattutto in seguito alle interazioni dell’utente con i componenti e sono ricevute dal Dispatcher
  • View che sono notificate, attraverso un evento che viene emesso dagli Store, della necessità di aggiornare i dati mostrati. Se in seguito all’interazione dell’utente, è necessaria una modifica dei dati dell’applicazione, dalle View verrà lanciata un’Action che verrà ricevuta dal Dispatcher.

Il concetto chiave su cui si basa Flux è il flusso unidirezionale dei dati. Qualsiasi richiesta di modifica dei dati contenuti negli Store deve avvenire sempre e solo attraverso il lancio di un’Action. Esistono diverse implementazioni di Flux oltre a quella ufficiale, per esempio potete consultare le pagine di Fluxxor e reflux.

Se volete conoscere maggiori dettagli sulla versione ufficiale di Flux, vi consiglio di leggere la documentazione o consultare la pagina ufficiale del progetto su Github.

Cos’è Redux

Redux è un’evoluzione di Flux, ne condivide alcuni dei principi, tra cui il concetto di flusso unidirezionale delle informazioni, ma si differrenzia per alcuni aspetti importanti che andremo ora a esaminare. Innanzitutto, Redux non usa il Dispatcher ma vedremo che vengono impiegate delle funzioni pure, chiamate Reducer, per aggiornare lo stato dell’applicazione. Mentre in Flux possono essere utilizzati più Store, Redux prevede l’esistenza di un unico Store che mantiene l’intero stato dell’applicazione. Proprio perché i Reducer sono funzioni pure, lo stato dell’applicazione non viene mai mutato. Ogni volta che i Reducer ricevono una nuova azione, processano l’azione ricevuta e, in caso sia necessario apportare delle modifiche allo stato, restituiscono sempre un nuovo oggetto (Per un uso efficiente della memoria possono essere usate librerie come Immutable che però non tratteremo in questa guida).

Dobbiamo sottolineare ancora una volta che anche Redux può essere utilizzato in qualsiasi applicazione Javascript e il suo uso non è limitato alla sola libreria React. Redux può essere tranquillamente usato in Angular, Vue.js, Polymer o in una applicazione scritta in puro codice Javascript, usando magari i Web Components (per chi non li conoscesse si tratta di una tecnologia nuova in fase di sviluppo ma già implementata in alcuni browser, potete leggere maggiori informazioni sul sito ufficiale)

Faremo ora una panoramica veloce su Redux, non vi preoccupate se qualche concetto può sembrare poco chiaro in quanto tratteremo in dettaglio il funzionamento di Redux già a partire dal prossimo articolo.

Redux si basa su tre principi fondamentali. Vediamoli in dettaglio uno alla volta.

Un solo Store

In Redux, esiste un solo Store (un’oggetto Javascript) che mantiene lo stato completo di tutta l’applicazione all’interno di un altro oggetto Javascript (oggetto State globale). Se consideriamo il caso di un’applicazione con una lista di piloti, in cui è possibile selezionare uno dei piloti per avere maggiori dettagli sulla sua carriera, statistiche ecc…, potremo avere, per esempio, un oggetto State come quello mostrato qui in basso.

// Oggetto State complessivo, faremo riferimento a questo oggetto nei prossimi paragrafi

// console.log(store.getState())

/* Output
  {
    currentDriver: 0,
    drivers: [
      {
        name: "Sebastian Vettel"
        scuderia: "Ferrari"
      },
      {
        name: "Lewis Hamilton"
        scuderia: "Mercedes"
      },
      {
        name: "Max Verstappen"
        scuderia: "Red Bull Racing"
      }
    ]
  }
*/

Il metodo getState() è uno dei metodi messi a disposizione da Redux per ottenere lo stato corrente dell’applicazione.

State è un oggetto di sola lettura

L’oggetto State è un oggetto di sola lettura. L’oggetto State non verrà mai modificato, ma, eventualmente, verrà restituito da un nuovo oggetto contentente le informazioni aggiornate. Quando è necessario modificare l’oggetto State, lo si farà sempre e soltanto attraverso il lancio di un’Action che è un oggetto Javascript in cui l’unica proprietà necessaria è la proprietà type. Possiamo definire altre proprietà all’interno dell’oggetto Action se ne abbiamo bisogno.(ovvero se dobbiamo passare altre informazioni alle funzioni Reducer) Solitamente i dati vengono passati all’interno di una proprietà payload anche se possiamo definire delle proprietà con nomi diversi. Nell’esempio qui in basso, all’interno dell’oggetto Action, abbiamo definito una proprietà chiamata "driver" oltre ovviamente alla proprietà "type".

store.dispatch({
  type: 'CURRENT_DRIVER',
  driver: 1
});

Abbiamo visto un altro metodo dell’oggetto store. Attraverso store.dispatch(action) lanciamo un’Action che sarà ricevuta dai Reducers i quali sono gli unici "autorizzati" ad aggiornare l’oggetto state.

L’oggetto State è aggiornato solo dai Reducer

L’oggetto State è modificato dai Reducer che sono delle funzioni pure. È importate sottolineare, ancora una volta, che, quando parleremo di modificare o aggiornare l’oggetto State, intendiamo sempre una sola cosa, ovvero restituire un nuovo oggetto State contenente le nuove informazioni. I Reducer, dicevamo, sono delle funzioni pure cioè delle funzioni che, dati certi argomenti in ingresso, restituiscono sempre lo stesso risultato. Una funzione pura non usa dei valori definiti in variabili globali, non effettua operazioni di I/O o chiamate remote che possono influenzare il valore restituito dalla funzione. Ogni volta che viene invocata con gli stessi argomenti, una funzione pura restituisce quindi sempre lo stesso valore.

// avremo potuto rinominare previousState lastValueOfCurrentDriverBeforeReceivingTheNewAction

function selectDriver(previousState = 0, action) {
  switch(action.type) {
    case 'CURRENT_DRIVER':
      return action.driver;
    default:
      return previousState;
  }
}

Nell’esempio abbiamo realizzato un Reducer che si occuperà di modificare solamente il valore della sola proprietà "currentDriver" dell’oggetto State globale che abbiamo visto sopra. La funzione effettuerà un aggiornamento solo se riceverà l’azione con proprietà "type" pari a ‘CURRENT_DRIVER’.

In generale è possibile combinare più reducer insieme. Possiamo creare dei reducer tali che ognuno si occupi di gestire una singola proprietà dell’oggetto State complessivo. Avremo quindi una funzione Reducer, a cui verrà passato l’oggetto State complessivo, che provvederà a delegare ad altri Reducer l’elaborazione dei valori di ciascuna proprietà presente nell’oggetto State.

Nel caso appena descritto, ogni reducer si occuperà di lavorare su una sola proprietà. Per cui, per esempio, il primo argomento previousState, con valore di default pari a zero, della funzione selectDriver() corrisponde al valore della proprietà currentDriver dell’oggetto State visto sopra. Ogni volta che verrà eseguita la funzione selectDriver(), Redux passerà come primo argomento l’ultimo valore di currentDriver presente nell’oggetto State. Dovremo quindi creare un altro Reducer che si occupi di gestire la proprietà ‘drivers’ e alla fine combinare i due Reducer in uno complessivo che gestisca l’intero oggetto State. (per esempio per aggiungere altri piloti alla lista o rimuovere qualche pilota o modificare le informazioni di ciascun pilota)

Vedremo che Redux mette a disposizione una funzione combineReducers() che ci permette di combinare i diversi reducer e restituisce un nuovo reducer.

Ricapitolando quanto detto, definito un oggetto State complessivo per la nostra applicazione con un determinato numero di proprietà, possiamo realizzare per ciascuna proprietà una funzione Reducer ognuna delle quali si occuperà di elaborare un pezzo dell’oggetto State complessivo, ovvero quello corrispondente alla proprietà dell’oggetto per cui la singola funzione Reducer è responsabile. Combineremo poi i vari Reducer per formare un’altra funzione Reducer, che per esempio possiamo chiamare rootReducer, che si occuperà di aggiornare l’intero oggetto State, delegando le singole parti dell’oggetto ai Reducer responsabili.

A partire dal prossimo articolo approfondiremo l’argomento e per i prossimi quattro articoli analizzeremo vai aspetti di Redux. Vedremo, inoltre, come sia possibile usare Redux all’interno delle applicazioni React grazie a react-redux.

Pubblicità