Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Composizione di reducer

In molti casi, è bene gestire lo stato delle nostre we application basate sul framework Redux combinando più reducer: ecco come fare.
In molti casi, è bene gestire lo stato delle nostre we application basate sul framework Redux combinando più reducer: ecco come fare.
Link copiato negli appunti

In applicazioni abbastanza semplici, come quella del nostro esempio, un reducer che prende in carico il compito di gestire le transizioni di stato può risultare sufficiente. Ma quando l'applicazione diventa un po' più complessa e, come spesso accade, anche il suo stato diventa complesso, la gestione delle transizioni con un solo reducer può diventare difficile, quanto meno in termini di leggibilità del codice e di gestione di un oggetto articolato che rappresenta lo stato dell'intera applicazione. In questo caso sarebbe opportuno razionalizzare la struttura dell'oggetto che rappresenta lo stato e suddividere il compito della gestione di porzioni dello stato tra più reducer. Proviamo a spiegare questo concetto con un esempio.

Supponiamo che, oltre alla gestione delle attività, la nostra applicazione debba anche gestire l'autenticazione dell'utente. In questo caso potremmo strutturare lo stato come mostrato di seguito:

{
  user:{
    userName:"mario.rossi",
    name:"Mario Rossi",
    token:"qpaje6djeci2j"
  },
  todoList:[
    {
      id:1,
      task:"Definire le azioni"
    },
    {
      id:2,
      task:"Definire i reducer"
    },
    {
      id:3,
      task:"Creare lo store"
    },
    {
      id:4,
      task:"Inviare le azioni allo store"
    }
  ]
}

Oltre ai dati sulle attività, avremmo anche i dati dell'utente autenticato. In questo caso potremmo riscrivere il reducer nel seguente modo:

const initialState = {
   user: {},
   todoList: []
};
function todo(state = initialState, action) {
   switch (action.type) {
      case ADD:
         state = Object.assign({}, state, {
            todoList: [...state.todoList, {
               id: state.todoList.length + 1,
               task: action.payload.task
            }]
         });
         break;
      case REMOVE:
         state = Object.assign({}, state, {
            todoList: state.todoList.filter((x) = > x.id != action.payload.taskId)
         });
         break;
      case USER_DATA:
         state = Object.assign({}, state, {
            user: {
               username: action.payload.userName,
               name: action.payload.name,
               token: action.payload.token
            }
         });
         break;
   }
   return state
}

Come possiamo vedere, abbiamo aggiunto la proprietà user allo stato iniziale e la gestione dell'azione USER_DATA all'interno del reducer. Oltre ad accrescere il codice all'interno del reducer, l'aumento della complessità dello stato porta il reducer stesso a doversi occupare di dati non strettamente correlati, creando difficoltà di comprensione del codice stesso.

La tecnica che di solito viene utilizzata per evitare ciò consiste nella creazione di più reducer ciascuno specializzato nella gestione di una porzione dello stato dell'applicazione. Nel nostro caso, invece di inserire la gestione dei dati dell'utente all'interno del reducer todo(), possiamo creare un nuovo reducer specializzato, come mostrato dal seguente esempio:

function todo(state = [], action) {
   switch (action.type) {
      case ADD:
         state = [...state, {
            id: state.todoList.length + 1,
            task: action.payload.task
         }];
         break;
      case REMOVE:
         state = todoList: state.todoList.filter((x) => x.id != action.payload.taskId);
         break;
   }
   return state
}
function user(state = {}, action) {
   switch (action.type) {
      case LOGIN:
         state = Object.assign({}, state, {
            username: action.payload.userName,
            name: action.payload.name,
            token: action.payload.token
         });
         break;
   }
   return state
}

Abbiamo riportato il reducer della lista delle attività ed aggiunto il reducer user() per la gestione dei dati dell'utente. A differenza della versione precedente, dove il reducer prendeva in carico la gestione dell'intero stato dell'applicazione, in questo caso ciascun reducer prende in carico soltanto la porzione di stato che gli compete. Questo si riflette sia nell'inizializzazione dello stato di default, che diventa un array vuoto nel caso di todo() e un oggetto vuoto nel caso di user()code>, sia nella composizione del nuovo stato da restituire. Vediamo infatti che in questa versione ciascun reducer non sa nulla della struttura globale dello stato, ma conosce soltanto la struttura della porzione di sua competenza. Questo fa si che i reducer siano tra loro indipendenti e che l'aggiunta di una nuova porzione di stato all'applicazione in linea di massima non dovrebbe influenzare i reducer esistenti.

Questa suddivisione logica della gestione dello stato è comoda per noi ma non per Redux, che ha bisogno in ogni caso di avere un'unica funzione per la gestione dello stato. Possiamo comunque mantenere questa separazione logica combinando più reducer tramite la funzione combineReducers:

import {combineReducers} from 'redux'
const appState = combineReducers({todo, user});

Come possiamo vedere dall'esempio, abbiamo importato la funzione combineReducers() dal modulo redux e l'abbiamo utilizzata per ottenere il reducer appState() a partire dai reducer todo() e user(). Questi ultimi sono passati a combineReducers() come metodi di un oggetto.

In realtà possiamo anche evitare di ricorrere a combineReducers(), semplicemente creando una funzione come la seguente:

function appState(state = {}, action) {
   return {
      todo: todo(state.todo, action),
      user: user(state.user, action)
   }
}

Questa funzione non è altro che il risultato della composizione effettuata internamente da combineReducers(): essa restituisce l'intero stato dell'applicazione combinando ciascuna porzione ottenuta dall'esecuzione dello specifico reducer.

Ti consigliamo anche