È uscito il Corso Java Completo
Torna al blog

Cos'è React e come funziona

Cos'è React e come funziona davvero: componenti, JSX, state e props, DOM virtuale, gli hook useState e useEffect e quando React è sovradimensionato.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

10 min di lettura

Hai scritto qualche pagina con JavaScript e a un certo punto il codice è diventato ingestibile: querySelector ovunque, elementi creati a mano, tre punti diversi del file che modificano lo stesso pezzo di pagina e uno di questi si dimentica sempre di aggiornarsi. È il problema che React risolve, e lo risolve capovolgendo il modo in cui pensi all'interfaccia. In questo articolo trovi cosa sono i componenti, come funzionano stato e proprietà, cos'è davvero il DOM virtuale e quando React non ti serve.

Cos'è

React è una libreria JavaScript per costruire interfacce utente a partire da componenti, in cui descrivi come deve apparire la pagina per un certo stato invece di modificarla manualmente passo dopo passo.

Nota la parola "libreria". Non è un dettaglio da pignoli.

Libreria, non framework: perché conta

La differenza tra libreria e framework sta in chi comanda. Un framework ti impone una struttura e chiama il tuo codice quando serve. Una libreria la chiami tu, dove vuoi.

React si occupa di una cosa sola: prendere lo stato della tua applicazione e trasformarlo in interfaccia. Tutto il resto — come navighi tra le pagine, come chiedi i dati al server, come organizzi le cartelle, come costruisci il progetto per la produzione — non è affare suo.

Questo ha una conseguenza pratica immediata: React da solo non basta a fare un'applicazione. Devi assemblare il resto, e quella è la ragione per cui esistono i meta-framework come Next.js. Ne parlo alla fine.

Il modello a componenti

Un componente è una funzione JavaScript che restituisce un pezzo di interfaccia. Niente di più esotico.

function Saluto({ nome }) {
  return <h1>Ciao, {nome}</h1>;
}

I componenti si compongono: ne metti uno dentro l'altro come faresti con i tag HTML.

function Pagina() {
  return (
    <div>
      <Saluto nome="Edoardo" />
      <Saluto nome="Marta" />
    </div>
  );
}

Il vantaggio non è tanto il riuso — quello viene dopo — quanto il fatto che puoi ragionare su un pezzo di interfaccia guardando un file solo. Se il pulsante si comporta male, il problema è nel componente del pulsante. Con il DOM manipolato a mano quella certezza non ce l'hai mai.

JSX: HTML dentro JavaScript

Quella sintassi con i tag dentro il codice si chiama JSX. Non è HTML e non è una stringa: è zucchero sintattico che uno strumento di build traduce in normali chiamate a funzione.

Le differenze da HTML che ti faranno sbagliare le prime volte:

  • class diventa className (class è una parola riservata in JavaScript)
  • for delle label diventa htmlFor
  • gli attributi sono in camelCase: onclickonClick, tabindextabIndex
  • ogni tag va chiuso, anche quelli che in HTML non lo richiedono: <img />, <br />
  • dentro le graffe metti espressioni JavaScript, non istruzioni: puoi scrivere un ternario, non un if
function Lista({ voci }) {
  return (
    <ul className="lista">
      {voci.map((voce) => (
        <li key={voce.id}>{voce.testo}</li>
      ))}
    </ul>
  );
}

Quel key non è decorativo: serve a React per capire quale elemento della lista corrisponde a quale, quando la lista cambia. Usare l'indice dell'array come chiave è l'errore classico, e si manifesta con campi di testo che si scambiano il contenuto quando riordini o cancelli elementi.

Il concetto che spiega tutto il resto

Qui sta il salto mentale, e se lo fai il resto di React diventa quasi ovvio.

L'interfaccia è una funzione dello stato. Tu non dici a React "aggiungi questa riga alla tabella" o "nascondi quel div". Tu dici: "questi sono i dati; ecco come deve apparire la pagina con questi dati". Poi cambi i dati, e React si occupa di far combaciare la pagina.

Con JavaScript puro faresti così:

// imperativo: elenchi i passi
const contatore = document.querySelector("#contatore");
let valore = 0;
document.querySelector("#bottone").addEventListener("click", () => {
  valore = valore + 1;
  contatore.textContent = valore;
});

Con React descrivi il risultato:

function Contatore() {
  const [valore, setValore] = useState(0);

  return (
    <div>
      <p>{valore}</p>
      <button onClick={() => setValore(valore + 1)}>Incrementa</button>
    </div>
  );
}

Nel primo caso devi ricordarti di aggiornare il testo ogni volta che cambi il valore. Se domani lo stesso valore compare in tre punti della pagina, hai tre aggiornamenti da non dimenticare. Nel secondo caso il problema non esiste: comparirà ovunque tu lo scriva, sempre aggiornato, perché la pagina viene ricalcolata a partire dallo stato.

Se il concetto di DOM ti è ancora nebuloso, vale la pena chiarirlo prima: è l'oggetto che React manipola al posto tuo.

State e props

Sono i due modi in cui i dati entrano in un componente, e vanno tenuti distinti.

StateProps
Da dove vieneNasce dentro il componenteArriva dal componente genitore
Chi lo cambiaIl componente stessoSolo chi lo passa
Modificabile?Sì, tramite la funzione setNo, è in sola lettura
Esempio tipicoUn campo di testo, un menu apertoIl nome da mostrare, una funzione da chiamare

I dati scendono dall'alto verso il basso. Se un componente figlio deve modificare qualcosa, il genitore gli passa una funzione da chiamare. Sembra macchinoso all'inizio ed è invece la cosa che rende l'applicazione tracciabile: per sapere da dove arriva un valore, risali.

L'errore più comune è duplicare lo stato. Se hai una lista di prodotti nello stato e ti serve il totale, non mettere anche il totale nello stato: calcolalo. Due fonti di verità per la stessa informazione vanno fuori sincrono prima o poi, ed è il genere di bug che ti fa perdere un pomeriggio.

// no: due stati che devono restare coerenti
const [prodotti, setProdotti] = useState([]);
const [totale, setTotale] = useState(0);

// sì: un solo stato, il resto è derivato
const [prodotti, setProdotti] = useState([]);
const totale = prodotti.reduce((somma, p) => somma + p.prezzo, 0);

DOM virtuale e riconciliazione, senza mitologia

Su questo argomento gira parecchia retorica, quindi vado dritto.

Quando lo stato cambia, React riesegue la funzione del componente e ottiene una descrizione di come dovrebbe essere l'interfaccia adesso. Non è HTML: sono semplici oggetti JavaScript. Questa descrizione è il DOM virtuale.

React confronta la nuova descrizione con quella precedente, individua le differenze e applica al DOM reale solo quelle. Questo confronto si chiama riconciliazione.

Due chiarimenti che mancano quasi sempre:

  • Il DOM virtuale non è magicamente veloce. Codice scritto a mano che tocca esattamente l'unico nodo da cambiare è più rapido di React. Il DOM virtuale ti dà prestazioni ragionevoli senza che tu debba pensarci, e il valore è lì, non nella velocità pura.
  • "Riesegue il componente" non significa "ricostruisce la pagina". La funzione gira di nuovo, il DOM reale viene toccato solo dove serve. Un componente che si rirenderizza spesso non è di per sé un problema.

Gli hook: useState e useEffect

Gli hook sono funzioni che danno ai componenti capacità che una funzione normale non avrebbe. Due regole non negoziabili: si chiamano sempre al livello più esterno del componente (mai dentro if, cicli o funzioni annidate) e solo dentro componenti o altri hook.

useState dichiara un valore che, cambiando, provoca un nuovo render:

const [testo, setTesto] = useState("");

useEffect serve a sincronizzare il componente con qualcosa che sta fuori da React: un timer, una sottoscrizione, il titolo del documento, un'API del browser.

useEffect(() => {
  const id = setInterval(() => setSecondi((s) => s + 1), 1000);
  return () => clearInterval(id); // pulizia: obbligatoria
}, []);

La funzione restituita è la pulizia, e ometterla è la fonte numero uno di perdite di memoria e di listener duplicati.

L'errore classico con useEffect

useEffect viene usato per cose che non sono effetti collaterali, ed è il singolo fraintendimento che costa più ore in React.

Il caso tipico è calcolare un valore derivato:

// sbagliato: due render, stato inutile, bug in agguato
const [nome, setNome] = useState("");
const [saluto, setSaluto] = useState("");

useEffect(() => {
  setSaluto(`Ciao ${nome}`);
}, [nome]);
// giusto: è solo un calcolo
const [nome, setNome] = useState("");
const saluto = `Ciao ${nome}`;

La regola pratica: se il valore si può ricavare da quello che hai già, calcolalo durante il render. Se qualcosa deve succedere in risposta a un'azione dell'utente, mettilo nel gestore dell'evento. useEffect è per quel che resta, cioè la sincronizzazione con il mondo esterno. È molto meno di quanto si creda.

L'altro incidente frequente è l'array delle dipendenze sbagliato: dimentichi un valore e leggi dati vecchi, oppure ci metti un oggetto creato a ogni render e l'effetto gira all'infinito.

Cosa React non ti dà

Riprendo il punto iniziale, adesso che ha senso. React non include:

  • Routing. Cambiare pagina con l'URL non è previsto. Serve una libreria.
  • Caricamento dati. Nessun meccanismo integrato per chiamare una API, gestire cache, errori e stati di caricamento.
  • Build. JSX va compilato: serve uno strumento come Vite.
  • Struttura del progetto. Nessuna convenzione. Decidi tu, e in team significa mettersi d'accordo.

Assemblare questi pezzi ogni volta è noioso e si sbaglia. Da qui i meta-framework: prendono React e ci aggiungono il resto con scelte già fatte. Il più diffuso è Next.js.

Quando React è la scelta giusta e quando è troppo

Ha senso quando c'è stato che cambia e si riflette in più punti dell'interfaccia: un carrello, una dashboard con filtri, un editor, un gestionale, qualsiasi cosa dietro un login. Più l'interfaccia è viva, più React ripaga.

È sovradimensionato per un sito vetrina: cinque pagine di contenuto, un modulo di contatto, magari un menu che si apre sul telefono. Lì React ti fa pagare uno strumento di build, un pacchetto JavaScript da scaricare e una complessità che non ti restituisce nulla. HTML, CSS e un po' di JavaScript fanno lo stesso lavoro, caricano prima e non invecchiano. Se il sito è pieno di contenuto ma quasi statico, un generatore come Astro è quasi sempre la risposta migliore: il confronto sta in quale framework scegliere.

Prima di partire con React, comunque, serve JavaScript vero: funzioni, destrutturazione, map, moduli, promesse. Chi arriva a React senza quelle basi finisce per attribuire a React confusione che è di JavaScript. Se la base traballa, il passaggio è imparare JavaScript da zero.

In sintesi

React è una libreria, non un framework: si occupa dell'interfaccia e lascia a te tutto il resto.

Il concetto centrale è che l'interfaccia è una funzione dello stato. Descrivi come deve apparire la pagina per certi dati, cambi i dati, e React calcola le differenze e aggiorna il DOM al posto tuo.

State e props non vanno confusi: lo stato nasce dentro il componente, le props arrivano dall'esterno e non si modificano. E niente valori derivati messi nello stato.

useEffect serve solo per sincronizzarti con il mondo fuori da React. Per i calcoli usa il render, per le azioni usa i gestori di eventi.

Per un sito vetrina non serve. React ripaga quando l'interfaccia ha stato che cambia davvero.

Il passo successivo naturale è vedere cosa ci si costruisce sopra: cos'è Next.js. Se invece ti interessa il lato server, il punto di partenza è cos'è Node.js.