Tra i molteplici framework JavaScript disponibili nell'ecosistema dello sviluppo web, React è una delle soluzioni più adottate oggi: ha lo scopo di semplificare lo sviluppo di interfacce visive grazie alla sua filosofia di utilizzo di componenti indipendenti e riutilizzabili.
Ogni componente è isolato dal resto dell'applicazione e utilizza due tipi di dati:
le props, informazioni passate al componente dal suo componente genitore
le state, informazioni gestite localmente dal componente
1. Problematica del prop-drilling
Quando un'applicazione React inizia a diventare più complessa, è comune vedere casi in cui uno sviluppatore passerà delle props a un componente solo affinché questo le trasmetta ai suoi componenti figli.
Questo caso generalmente non pone problemi quando si tratta di passare una prop tra 2 o 3 livelli di componenti.
Tuttavia, in alcuni casi può succedere che l'informazione venga passata a più di 5 livelli diversi nell'albero dei componenti (ad esempio, la gestione dei dati relativi all'autenticazione).
Chiamiamo questa problematica il prop-drilling, in altre parole, si passano dati attraverso un gran numero di componenti mentre una minoranza utilizza l'informazione trasmessa. Per risolvere questo problema, una delle soluzioni più utilizzate è quella di implementare uno state globale, ovvero informazioni accessibili a tutta l'applicazione. Questo può essere fatto con soluzioni come Redux o la Context API di React.
2. Redux & Context API
Redux
Redux è una libreria esterna a React che si focalizza sulla gestione dei dati nelle applicazioni JavaScript. È importante notare che Redux può anche essere utilizzata con altre applicazioni web realizzate senza React, ma è regolarmente associata a questo framework.
Redux mette a disposizione uno store, un oggetto JavaScript che contiene le diverse informazioni memorizzate globalmente. La libreria react-redux mette poi a disposizione due hooks molto pratici, useSelector e useDispatch, che ci permettono di recuperare i dati memorizzati globalmente, ma anche di attivare azioni che consentono di modificare questo state global.
Prerequisiti npm install --save redux react-redux
Queste 2 dipendenze devono essere installate per iniziare a usare Redux su un'applicazione React.
Implementazione dello store Redux language=js import { createStore } from "redux"; import { Provider } from "react-redux"; import { render } from "react-dom"; import reducer from "./reducer"; const store = createStore(reducer); const App = () => ( <Provider store={store}> <Page /> </Provider> ); render(<App />, document.getElementById("root"));
Questa prima parte di codice permette di implementare lo store messo a disposizione da Redux. Corrisponde all'oggetto che conterrà i dati "globali" condivisi dal resto dell'applicazione.
Implementazione del "reducer" di Redux language=js const initialState = { isLogged: false }; export default function reducer(state = initialState, action) { switch (action.type) { case "SET_IS_LOGGED": return { ...state, isLogged: action.payload }; default: return state; } };
Questa funzione corrisponde al reducer utilizzato da Redux. In questa funzione, si definiscono i dati globali e le diverse actions che permettono di modificare i dati globali del store.
Un reducer riceve 2 parametri, il state globale attualmente memorizzato in Redux (dove si specifica il state iniziale) e successivamente le actions che verranno ricevute dal reducer. Un'action corrisponde a una "richiesta" di modifica del state globale, essa è rappresentata da un oggetto JavaScript che include almeno una chiave "type".
Il type dell'action comprende una stringa di caratteri unica che verrà utilizzata per identificare quali sono i dati da modificare nel store di Redux.
Nel frammento di codice qui sopra, si utilizza uno switch che permette di alternare il comportamento del reducer in funzione del type dell'action.
Recupero dei dati globali e modifiche dei dati language=js import React from "react"; import { useSelector, useDispatch } from "react-redux"; export default function DeeplyNestedComponent() { const globalState = useSelector((state) => state); const dispatch = useDispatch(); return ( <div> {globalState.isLogged ? ( <button onClick={() => dispatch({ type: "SET_IS_LOGGED", payload: false })}> Log out </button> ) : ( <button onClick={() => dispatch({ type: "SET_IS_LOGGED", payload: true })}> Log in </button> )} </div> ); }
Utilizzando gli hooks useSelector e useDispatch, si possono recuperare le informazioni memorizzate globalmente e cambiare dinamicamente la visualizzazione dei pulsanti di autenticazione. Effettuando il "dispatch di un'action", questo ci permette (nel nostro esempio) di connetterci o di disconnetterci.
useSelector : Hook che permette di recuperare i dati che sono memorizzati globalmente, da qualsiasi componente
useDispatch : Hook che permette di recuperare una funzione "dispatch" che permette di inviare delle actions (sotto forma di oggetti) che verranno elaborate dal reducer implementato più in alto
Puoi andare ancora oltre con Redux seguendo la documentazione disponibile su
il loro sito.
Context API
Apparsa ufficialmente a Marzo 2018, la Context API di React permette di memorizzare informazioni in un "contesto". Il contesto mette a disposizione un provider (un componente React) che permette a tutti i componenti figli di accedere all'informazione memorizzata nel contesto. Il suo utilizzo si è notevolmente semplificato con l'arrivo dell'hook useContext messo a disposizione nativamente in React.
Creazione del contesto language=js import React, { useState, createContext } from "react"; const AuthContext = createContext(); const AuthProvider = (props) => { const [authState, setAuthState] = useState({ isLogged: false }); return ( <AuthContext.Provider value={[authState, setAuthState]}> {props.children} </AuthContext.Provider> ); }; export { AuthContext, AuthProvider };
Come Redux, la Context API mette a disposizione un provider, il componente incaricato di passare l'informazione memorizzata globalmente. Tuttavia, contrariamente alla nozione di dispatch e actions di Redux, qui trasmettiamo tramite il contesto uno state e uno setState. In questo contesto, possiamo facilmente recuperare e modificare l'informazione memorizzata globalmente, come se lo facessimo in un componente locale. La Context API non restringe il type di informazione che può essere trasferito. Puoi benissimo fornire un valore (una stringa di caratteri, un numero, una data, …), oppure un oggetto o un array di elementi.
Implementazione del contesto language=js import React from "react"; import { AuthProvider } from "./contexts/auth-context"; import AppRouter from "./navigation/app-router"; export default function App() { return ( <div> <AuthProvider> <AppRouter /> </AuthProvider> </div> ); }
Come nel contesto di Redux, posizioniamo il provider alla radice dell'applicazione affinché l'informazione sia disponibile ovunque. È interessante notare che questa decisione è puramente arbitraria: il store di Redux è generalmente sempre posizionato alla radice dell'applicazione, mentre un contesto può benissimo essere posizionato più in profondità nell'albero dei componenti dell'applicazione, si potrebbe per esempio applicarlo a una schermata in particolare.
Recupero e modifiche dei dati globali del contesto language=js import React, { useContext } from "react"; import { AuthContext } from "./context"; function AuthButtons() { const [authState, setAuthState] = useContext(AuthContext); return ( <div> {authState.isLogged ? ( <button onClick={setAuthState((state) => ({ ...state, isLogged: false }))}> Log out </button> ) : ( <button onClick={setAuthState((state) => ({ ...state, isLogged: true }))}> Log in </button> )} </div> ); } export default AuthButtons;
L'utilizzo dell'hook useContext ci permette di recuperare il nostro authState e il nostro setAuthState che abbiamo passato nel parametro "value" del nostro provider.
3. Confronto
Redux e la Context API sono due soluzioni valide per mettere a disposizione un state globale. Ora elencheremo i punti positivi e negativi di ogni soluzione per permetterti di selezionare l'opzione migliore.
Redux
Vantaggi :
Architettura delle responsabilità (reducers, actions, …) ben definita e costante
Estensioni del browser come "Redux DevTools" sono disponibili e permettono di fare facilmente il debug dell'integrazione di Redux in un progetto che utilizza React
Svantaggi :
Context API
Vantaggi :
Soluzione nativa alla libreria React, è disponibile non appena si inizia una nuova applicazione React
Molto facile da usare
È possibile integrare contesti più "locali", cioè che non inglobano l'intera applicazione ma piuttosto una piccola quantità di componenti: questo permette di segmentare la logica dell'applicazione
Svantaggi :
La Context API non è adatta per cambiamenti frequenti di state (ad esempio, per il valore di un campo di testo)
Tutti i componenti che consumano il contesto verranno re-render ad ogni cambiamento dello state globale proposto dal contesto, anche se l'informazione che utilizzano non è cambiata
Conclusione
Redux è una libreria utilizzata dal 2015, è presente in una grandissima quantità di applicazioni React in produzione. È stata testata e approvata in numerose occasioni e rimarrà un'opzione particolarmente interessante quando un'applicazione React inizia a crescere e ad avere una base di codice consistente.
Tuttavia, se desideri integrare alcuni dati globali rapidamente e in modo più semplificato, la Context API sembra poter soddisfare le tue aspettative. È integrata direttamente in React.
Nessuna di queste due opzioni è predominante. In base alla portata del tuo progetto e all'implementazione che desideri realizzare, una di queste 2 soluzioni ti permetterà di gestire i dati globali della tua applicazione.
Per saperne di più, ti propongo i seguenti link che offrono informazioni aggiuntive che potrebbero orientare più precisamente la tua scelta: