Frontend

React e Redux: concetti base

Autore

Manuel Ricci

Redux è una libreria open-source di JavaScript per la gestione dello stato delle applicazioni. È particolarmente utile per applicazioni con architetture complesse e molti dati dinamici che necessitano di essere sincronizzati tra diverse parti dell'applicazione, come per esempio in progetti basati su React.

Basi di Redux

Redux si basa su tre principi fondamentali:

  1. Single source of truth: Lo stato dell'intera applicazione è conservato all'interno di un albero di oggetti all'interno di un unico store. Questo rende più facile il debug e l'ispezione dello stato.
  2. Stato è di sola lettura: L'unico modo per modificare lo stato è emettere un'azione, che è un oggetto che descrive cosa è successo.
  3. Le modifiche vengono fatte con funzioni pure: Per specificare come lo stato dell'applicazione cambia in risposta ad un'azione, si usano dei reducer, che sono funzioni pure che prendono lo stato precedente e un'azione, e restituiscono il nuovo stato.

Componenti chiave di Redux

  • Store: Contiene lo stato dell'applicazione.
  • Action: Un oggetto che descrive una modifica nello stato.
  • Reducer: Una funzione che ritorna il nuovo stato basato su (stato corrente, azione).

Differenza tra Redux e Context API

L'uso di Redux rispetto alle Context API in applicazioni React è una scelta architetturale che dipende da vari fattori, tra cui la complessità dell'applicazione, le esigenze di gestione dello stato e le preferenze del team di sviluppo. Ecco alcuni motivi per cui si potrebbe preferire Redux alle Context API:

1. Gestione dello Stato Prevedibile

Redux offre un modello di gestione dello stato altamente prevedibile grazie ai suoi tre principi fondamentali. Questo rende più facile ragionare sull'applicazione, soprattutto quando diventa grande e complessa. Redux facilita anche il debugging, grazie a strumenti come Redux DevTools, che permettono di visualizzare, tracciare e manipolare lo stato dell'applicazione in tempo reale.

2. Middleware e Estendibilità

Redux supporta l'uso di middleware, che consente di inserire logica personalizzata durante il dispatching delle azioni. Questo è particolarmente utile per operazioni asincrone, logging, crash reporting e altre funzionalità che necessitano di intercettare o modificare le azioni prima che raggiungano i reducer. Le Context API, sebbene possano essere usate in combinazione con useReducer per una certa estendibilità, non offrono nativamente lo stesso livello di supporto per middleware.

3. Scalabilità

Redux scala bene con applicazioni di grandi dimensioni e complesse, fornendo un framework consistente e prevedibile per la gestione dello stato. La sua architettura incentrata sull'unica fonte di verità e sull'immutabilità dello stato facilita la gestione di grandi quantità di dati e il coordinamento tra componenti lontani nell'albero dei componenti. Le Context API possono causare rerender non necessari in consumatori profondi nell'albero dei componenti se non gestite attentamente, specialmente in app grandi.

4. Ecosistema e Comunità

Redux ha un vasto ecosistema e una comunità di sviluppatori molto attiva. Ci sono molte librerie e strumenti complementari che possono essere integrati con Redux per estenderne le funzionalità. Inoltre, la vasta documentazione, le guide e le risorse di apprendimento disponibili per Redux possono facilitare l'adozione e la risoluzione dei problemi.

5. Pattern e Pratiche Consigliate

Redux incoraggia lo sviluppo seguendo pattern e pratiche consigliate ben definite, che possono aiutare i team di sviluppo a scrivere codice più organizzato e manutenibile. L'architettura di Redux promuove anche la separazione delle preoccupazioni, rendendo più chiaro dove e come le modifiche allo stato dovrebbero essere gestite.

Mentre Redux offre questi vantaggi, non è sempre la scelta giusta per ogni progetto. Per applicazioni piccole o semplici, l'uso delle Context API può essere sufficiente e più diretto. Le Context API sono anche più leggere e meno invasive, dato che sono integrate direttamente in React. La scelta tra Redux e Context API dipende quindi dalle esigenze specifiche del progetto, dalla complessità dello stato da gestire e dalle preferenze del team di sviluppo.

Esempio pratico di Redx

Giunti a questo punto di teoria ne abbiamo accumulata abbastanza, diamoci da fare con un esempio pratico.

Prima di tutto, installiamo Redux Toolkit e React-Redux:

1npm install @reduxjs/toolkit react-redux

Configurazione dello Store con Redux Toolkit

Redux Toolkit fornisce la funzione configureStore per semplificare la configurazione dello store. Creiamo un file store.ts utilizzando Redux Toolkit:

1// store.ts
2import { configureStore, createSlice, PayloadAction } from '@reduxjs/toolkit';
3
4// Definizione dello stato iniziale
5type TodoState = string[];
6
7// Creazione di uno slice che include reducer e azioni
8const todosSlice = createSlice({
9  name: 'todos',
10  initialState: [] as TodoState,
11  reducers: {
12    addTodo: (state, action: PayloadAction<string>) => {
13      state.push(action.payload);
14    },
15        removeTodo: (state, action: PayloadAction<number>) => {
16            state.splice(action.payload, 1);
17        }
18  },
19});
20
21export const { addTodo, removeTodo } = todosSlice.actions;
22
23const store = configureStore({
24  reducer: {
25    todos: todosSlice.reducer,
26  },
27});
28
29// Tipizzazione dello stato globale
30export type RootState = ReturnType<typeof store.getState>;
31
32export default store;

Creazione di Componenti React con React-Redux

Aggiorniamo il componente App per utilizzare useDispatch e useSelector da React-Redux insieme agli slice di Redux Toolkit:

1// App.tsx
2import React, { useState } from 'react';
3import { useDispatch, useSelector } from 'react-redux';
4import { RootState, addTodo, removeTodo } from './redux/store';
5
6const App: React.FC = () => {
7  const [todo, setTodo] = useState('');
8  const todos = useSelector((state: RootState) => state.todos);
9  const dispatch = useDispatch();
10
11  const handleAddTodo = () => {
12    dispatch(addTodo(todo));
13    setTodo('');
14  };
15
16    const handleRemoveTodo = (index: number) => {
17        dispatch(removeTodo(index));
18    }
19
20  return (
21    <div>
22      <input type="text" value={todo} onChange={(e) => setTodo(e.target.value)} />
23      <button onClick={handleAddTodo}>Add Todo</button>
24      <ul>
25        {todos.map((todo, index) => (
26          <li key={index}>{todo} <button onClick={() => handleRemoveTodo(index)}>Elimina</button></li>
27        ))}
28      </ul>
29    </div>
30  );
31};
32
33export default App;
1// index.tsx
2import React from 'react';
3import ReactDOM from 'react-dom';
4import { Provider } from 'react-redux';
5import App from './App';
6import store from './store';
7
8ReactDOM.render(
9  <Provider store={store}>
10    <App />
11  </Provider>,
12  document.getElementById('root')
13);

Esecuzione dell'Applicazione

Dopo aver aggiornato i file come indicato sopra, puoi eseguire l'applicazione con il seguente comando, assumendo che tu stia utilizzando Create React App o una configurazione simile:

1npm start

Questo approccio moderno con Redux Toolkit e React-Redux semplifica la configurazione dello store, la definizione di reducer e azioni, e l'integrazione con componenti React, rendendo il codice più leggibile e manutenibile.

Caricamento...

Diventiamo amici di penna? ✒️

Iscriviti alla newsletter per ricevere una mail ogni paio di settimane con le ultime novità, gli ultimi approfondimenti e gli ultimi corsi gratuiti puubblicati. Ogni tanto potrei scrivere qualcosa di commerciale, ma solo se mi autorizzi, altrimenti non ti disturberò oltre.

Se non ti piace la newsletter ti ricordo la community su Discord, dove puoi chiedere aiuto, fare domande e condividere le tue esperienze (ma soprattutto scambiare due meme con me). Ti aspetto!

Ho in previsione di mandarti una newsletter ogni due settimane e una commerciale quando capita.