MAISON CODE .
/ Tech · React · State Management · Architecture · Performance

Gestione dello Stato: l'atomico contro il monolite

Redux è (quasi) morto. Il contesto è una trappola. Un approfondimento tecnico sulla moderna gestione dello stato di React: Zustand, Jotai e TanStack Query.

AB
Alex B.
Gestione dello Stato: l'atomico contro il monolite

Al centro di ogni applicazione React c’è lo “Stato”. Se l’interfaccia utente è una funzione dello stato (€UI = f(state)€), la gestione di tale stato rappresenta la decisione ingegneristica più critica da prendere.

Per anni abbiamo avuto Redux. Era prolisso, centralizzato e immutabile. Ha funzionato, ma hai scritto più standard che codice. Poi abbiamo avuto Contesto. Era integrato e semplice. Ci siamo precipitati tutti a usarlo. Quindi le nostre app hanno iniziato a rallentare. La digitazione di un input di testo ha causato il nuovo rendering della barra laterale. Ci siamo resi conto che: Il contesto è uno strumento di inserimento delle dipendenze, non uno strumento di gestione dello stato.

Nel 2025, l’ecosistema è maturo. Ora classifichiamo lo stato in tre livelli distinti, ciascuno con uno strumento specializzato.

Perché Maison Code ne parla

Abbiamo osservato il rendering dell’app di un cliente 4.000 volte al secondo a causa di un singolo provider di contesto. Li abbiamo spostati in un’architettura a stato a tre strati:

  • Stato server: TanStack Query (per memorizzazione nella cache e idratazione).
  • Stato cliente globale: Zustand (per carrello e tema).
  • Stato locale: segnali o useState (per gli input del modulo). Ciò ha ridotto il tempo di blocco del thread principale di 600 ms e migliorato i punteggi INP a <50 ms. Riteniamo che la gestione dello stato sia la causa numero 1 di rallentamento delle prestazioni nelle app React.

La Teoria: I Tre Strati

1. Stato del server (la cache)

Dati che risiedono su un server remoto. Non lo “possiedi”; lo “prendi in prestito”. Può diventare stantio.

  • Esempi: Profilo Utente, Elenco Prodotti, Ordini.
  • Strumento: TanStack Query (React Query) o SWR.
  • Anti-Pattern: inserimento manuale dei dati API in Redux/Zustand. Si finisce per reinventare la memorizzazione nella cache, la deduplicazione e gli stati di caricamento.

2. Stato del cliente (l’interfaccia utente)

Dati che esistono solo nel browser e sono condivisi tra i componenti.

  • Esempi: La modalità modale è aperta? Qual è il tema attuale? Cosa c’è nel carrello?
  • Strumento: Zustand (per negozi globali) o Jotai (per aggiornamenti atomici).

3. Stato locale (il componente)

Dati isolati in un singolo componente o nei suoi figli diretti.

  • Esempi: il menu a discesa è aperto? Qual è il valore di questo specifico campo di input?
  • Strumento: useState / useReducer.

Il problema del contesto

Perché non usare semplicemente createContext per tutto? A causa della reattività a grana grossa.

“tsx const DataContext = createContext();

funzione Fornitore({ figli }) { const [nome, setNome] = useState(“Alex”); const [tema, setTheme] = useState(“Scuro”);

// Questo riferimento all’oggetto cambia con QUALSIASI aggiornamento valore const = { nome, tema, setName, setTheme };

return <DataContext.Provider valore={valore}>{children}</DataContext.Provider>; }


Se viene chiamato `setName("John")`:
1. L'oggetto "valore" viene ricreato.
2. **Ogni componente** che chiama `useContext(DataContext)` viene nuovamente renderizzato.
3. Anche il componente `ThemeToggler` viene ri-renderizzato, anche se `theme` non è cambiato!

Puoi ottimizzarlo con `useMemo` e dividere i contesti (`NameContext`, `ThemeContext`), ma questo porta a "Context Hell" (Pyramid of Doom).

## La Soluzione 1: Zustand (La Sostituzione del Monolite)

**Zustand** è più o meno "Redux senza il boilerplate".
Utilizza un'architettura Store, ma risolve il problema del re-render con i **Selettori**.

"tsx
importa { crea } da 'zustand';

const useStore = create((set) => ({
  orsi: 0,
  pesce: 0,
  increaseBears: () => set((state) => ({ bears: state.bears + 1 })),
  incrementoFish: () => set((state) => ({ fish: state.fish + 1 })),
}));

funzione ContaOrso() {
  // SELETTORE: questo componente osserva SOLO gli "orsi"
  const orsi = useStore((stato) => stato.orsi);
  return <h1>{orsi} Orsi</h1>;
}

Se viene chiamato increaseFish(), BearCounter non esegue nuovamente il rendering. Zustand confronta il valore restituito dal selettore (state.bears). Se non è cambiato, salta l’aggiornamento.

Potenza del middleware

Zustand ha per lo più dimensioni di 1 KB, ma un middleware potente. Persist: salva automaticamente lo stato in “localStorage”. Immer: consente la sintassi modificabile (state.bears++) negli aggiornamenti.

“tsx import { persist } da ‘zustand/middleware’;

const useCartStart = crea( persistere( (impostato) => ({ elementi: [], addItem: (id) => set((state) => ({ elementi: […state.items, id] })), }), { nome: ‘cart-storage’ } // Digita localStorage ) );


### Accedi ai componenti esterni
Fondamentalmente, è possibile accedere ai negozi Zustand al di fuori di React (ad esempio, nelle funzioni di utilità o negli intercettori API).

```javascript
// api.ts
import { useAuthStore } da './authStore';

const token = useAuthStore.getState().token; // Accesso senza hook
fetch('/api', { headers: { Autorizzazione: token } });

La soluzione 2: Jotai (L’approccio atomico)

Jotai (ispirato da Recoil) adotta un approccio diverso. Invece di un grande negozio, hai migliaia di minuscoli atomi. Lo Stato si costruisce dal basso verso l’alto.

“tsx importa {atomo, usaAtom} da ‘jotai’;

// Dichiara gli atomi const prezzoAtom = atomo(10); const quantitàAtom = atomo(2);

// Atomo calcolato (derivato). const totalAtom = atom((get) => get(priceAtom) * get(quantityAtom));

funzione Carrello() { const [totale] = useAtom(totaleAtom); return

Totale: {total}
; }


**Caso d'uso**: app altamente interattive come un foglio di calcolo, un editor di diagrammi o un dashboard in cui le dipendenze sono complesse.
Se aggiorni "priceAtom", solo i componenti che ascoltano "price" o "total" vengono nuovamente visualizzati. È precisione chirurgica.

## Stato del server: gestione di Async

Sosteniamo fortemente **TanStack Query**.
Gestisce le parti difficili dello stato asincrono:
* **Stale-While-Revalidate**: mostra i vecchi dati durante il recupero di quelli nuovi.
* **Recupero messa a fuoco**: aggiorna i dati quando l'utente torna alla finestra.
* **Deduplicazione**: se 10 componenti richiedono il profilo utente, viene effettuata solo 1 richiesta di rete.

"tsx
const { dati, sta caricando } = useQuery({
  queryKey: ['utente', id],
  queryFn: () => fetchUser(id),
  staleTime: 1000 * 60, // I dati sono aggiornati per 1 minuto
});

Mescolarlo con Zustand è comune. Zustand detiene lo stato di “filtro”. React Query contiene i dati “list” derivati ​​da quel filtro.

Sommario: La matrice decisionale

RequisitoRaccomandazionePerché?
Dati APIQuery TanStackCaching, deduplicazione e stati di caricamento integrati.
Interfaccia utente globaleZustandI selettori Semplice, Piccolo impediscono il re-rendering.
Grafico complessoJotaiIl monitoraggio della dipendenza atomica è potente.
Forma StatoModulo React HookGli input incontrollati funzionano meglio.

10. Segnali: il futuro? (Preatto/Solido)

React esegue nuovamente il rendering dei componenti. I segnali (adottati da Preact, Solid, Vue, Angular) aggiornano direttamente il DOM. cont const = segnale(0); <div>{count}</div> Quando si verifica count.value++, la funzione Component NON viene eseguita nuovamente. Solo il nodo di testo negli aggiornamenti DOM. Questa è la complessità O(1). React sta esplorando questo aspetto con “React Compiler” (Forget), ma Signals è una primitiva fondamentalmente più efficiente per la reattività a grana fine. Monitoriamo attentamente questo spazio. Per dashboard ad alte prestazioni (trading di criptovalute), a volte utilizziamo Preact + Signals invece di React.

11. Stato persistente (prima locale)

Zustand persistere è semplice. Ma che dire dei dati Offline-First validi? Ci stiamo muovendo verso il Local-First Software (LoFi). Strumenti come RxDB o Replicache. Lo “State Manager” è in realtà un database locale che si sincronizza in background. Questo tratta il Server come una fonte di verità “Secondaria”. Il Cliente è Primario. Questa architettura rende le app istantanee (latenza 0 ms).

13. Il modello dell’osservatore (MobX)

Prima di Signals c’era MobX. Utilizza oggetti “Osservabili” e “Osservatori” (Componenti). Sembra magico. Muti un oggetto user.name = "John" e il componente si aggiorna. Non consigliamo MobX per nuovi progetti perché nasconde troppa complessità (Magic Proxies). È notoriamente difficile eseguire il debug di “Perché è stato eseguito questo rendering?”. Tuttavia, per app molto specifiche e ad alta densità di dati (come un foglio di calcolo), MobX è più veloce di Redux grazie al monitoraggio capillare delle dipendenze.

14. Redux Toolkit (RTK): la modernizzazione

Se devi utilizzare Redux (Enterprise Legacy), utilizza RTK. Si comporta come Zustand.

  • Nessuna istruzione “switch”.
  • Immer integrato (sintassi mutabile).
  • Query RTK integrata (simile a TanStack Query). La migrazione dal vecchio Redux a RTK riduce la dimensione del codice del 60%. Ma se inizi da capo: Zustand è 1KB, RTK è 40KB. Scegli saggiamente.

15. Onorare i caduti: rinculo

Dobbiamo menzionare Recoil (Meta). Ha inventato il concetto “Atom” per React. Ma al momento non è mantenuto (Meta è stato spostato su altri strumenti interni). Jotai prese la torcia. Se disponi di un codebase Recoil, migra su Jotai. L’API è simile al 90% (useRecoilState -> useAtom). Non iniziare nuovi progetti con Recoil.

16. Conclusione

La gestione dello Stato non si occupa più di trovare “L’unica biblioteca che li governi tutti”. Si tratta di comporre strumenti specializzati. In Maison Code, per impostazione predefinita utilizziamo lo stack Zusstand + React Query. È robusto, performante e facile da usare per gli sviluppatori.


Refactoring di Redux legacy?

La tua applicazione è sepolta sotto boilerplate e riduttori lenti?

Assumi i nostri architetti.