Redux Saga è una libreria middleware di Redux che semplifica la gestione degli effetti collaterali (side effect) nelle applicazioni Redux. Sfruttando al massimo le funzionalità dei Generatori (function*
) di ES6, permette di scrivere codice asincrono che assomiglia a codice sincrono.
Redux Saga non esiste solo nel mondo JavaScript, ma è considerato anche un design pattern. Il Saga pattern è un modo per gestire transazioni lunghe con molti effetti collaterali o potenziali rischi. Per ogni transazione completata con successo, è necessaria una contro-transazione per ripristinare lo stato iniziale in caso di problemi.
Diagramma del Saga Pattern nella gestione delle transazioni
Gli effetti collaterali sono azioni nelle funzioni di programmazione che interagiscono con il mondo esterno, come chiamare un’API per recuperare dati, salvare dati in localStorage o leggere i cookie dal browser. Queste azioni sono spesso asincrone e possono modificare lo stato dell’applicazione. I Reducer in Redux devono essere sincroni e puri, ovvero devono elaborare la logica solo in base all’input e restituire l’output senza causare effetti collaterali. Pertanto, Redux Saga viene utilizzato per gestire gli effetti collaterali all’esterno dei Reducer, mantenendoli semplici e facili da testare.
Una funzione Generator è un tipo speciale di funzione in JavaScript che può sospendere l’esecuzione e restituire un valore più volte. La parola chiave yield
viene utilizzata per sospendere l’esecuzione e restituire un valore. A differenza delle funzioni normali che vengono eseguite e restituiscono un risultato una sola volta, una funzione Generator può essere eseguita, sospesa per restituire un risultato e quindi riprendere l’esecuzione dal punto in cui si era interrotta.
Redux Saga funziona ascoltando le azioni inviate (dispatch). Quando un’azione viene inviata, Redux Saga verifica se si tratta di un’azione di suo interesse. In tal caso, esegue una funzione generatore corrispondente. Questa funzione generatore restituirà degli oggetti effetto tramite yield
. Gli oggetti effetto sono oggetti che contengono informazioni che indicano al middleware di Redux di eseguire altre operazioni, come chiamare una funzione asincrona o inviare una nuova azione allo store.
Perché usare Redux Saga? Rispetto a Redux Thunk, un altro middleware comunemente usato per gestire gli effetti collaterali, Redux Saga offre molti vantaggi. Redux Thunk spesso porta al “callback hell” quando si gestiscono più attività asincrone annidate, rendendo il codice difficile da leggere e manutenere. Redux Saga, grazie all’uso di funzioni generatore e oggetti effetto, rende il codice più leggibile, comprensibile e testabile. Redux Saga permette di testare facilmente i flussi asincroni e di mantenere le azioni pure.
Esempio di confronto tra Redux Thunk e Redux Saga nella gestione di una richiesta API:
Redux Thunk:
import { API_BUTTON_CLICK, API_BUTTON_CLICK_SUCCESS, API_BUTTON_CLICK_ERROR } from './actions/consts';
import { getDataFromAPI } from './api';
const getDataStarted = () => ({ type: API_BUTTON_CLICK });
const getDataSuccess = data => ({ type: API_BUTTON_CLICK_SUCCESS, payload: data });
const getDataError = message => ({ type: API_BUTTON_CLICK_ERROR, payload: message });
const getDataFromAPI = () => {
return dispatch => {
dispatch(getDataStarted());
getDataFromAPI()
.then(data => {
dispatch(getUserSuccess(data));
})
.catch(err => {
dispatch(getDataError(err.message));
});
};
};
Redux Saga:
import { call, put, takeEvery } from 'redux-saga/effects';
import { API_BUTTON_CLICK, API_BUTTON_CLICK_SUCCESS, API_BUTTON_CLICK_ERROR } from './actions/consts';
import { getDataFromAPI } from './api';
export function* apiSideEffect(action) {
try {
const data = yield call(getDataFromAPI);
yield put({ type: API_BUTTON_CLICK_SUCCESS, payload: data });
} catch (e) {
yield put({ type: API_BUTTON_CLICK_ERROR, payload: e.message });
}
}
export function* apiSaga() {
yield takeEvery(API_BUTTON_CLICK, apiSideEffect);
}
Redux Saga separa la logica di gestione degli effetti collaterali dai componenti e dai reducer, rendendo il codice più facile da manutenere, estendere e testare. L’uso di try/catch
in Redux Saga semplifica la gestione degli errori rispetto all’uso di catene di promesse in Redux Thunk.