8 min

Modelli mentali di React: il modo di pensare di Dan Abramov

I modelli mentali di React possono rendere React più semplice: apprendi le idee chiave su componenti, rendering, stato ed effetti, poi applicale per costruire interfacce velocemente tramite chat.

Modelli mentali di React: il modo di pensare di Dan Abramov

Perché React sembra difficile finché non hai i modelli mentali giusti

React può sembrare frustrante all'inizio perché vedi l'interfaccia cambiare, ma non sempre riesci a spiegare perché è cambiata. Clicchi un pulsante, qualcosa si aggiorna e poi una parte diversa della pagina ti sorprende. Di solito non è “React è strano”. È “la mia idea di cosa fa React è sfocata”.

Un modello mentale è la storia semplice che ti racconti su come funziona qualcosa. Se la storia è sbagliata, prenderai decisioni sicure che portano a risultati confusi. Pensa a un termostato: un modello povero è “imposto 22°C e la stanza diventa subito 22°C”. Un modello migliore è “imposto un obiettivo, e il riscaldamento si accende e spegne nel tempo per raggiungerlo”. Con la storia migliore, il comportamento smette di sembrare casuale.

React funziona allo stesso modo. Una volta che adotti poche idee chiare, React diventa prevedibile: puoi guardare i dati correnti e indovinare affidabilmente cosa sarà sullo schermo.

Dan Abramov ha aiutato a diffondere questa mentalità del “rendilo prevedibile”. L'obiettivo non è memorizzare regole. È tenere poche verità in testa così puoi fare debug ragionando, non con tentativi ed errori.

Tieni a mente queste idee:

  • L'interfaccia è il risultato dello stato, non di una serie di modifiche manuali al DOM.
  • I componenti sono funzioni che descrivono come dovrebbe apparire l'interfaccia.
  • I dati scorrono verso il basso; i cambiamenti risalgono tramite eventi.
  • Gli effetti sono separati dal rendering e hanno uno scopo specifico.

Conservale e React smetterà di sembrare magia. Comincerà a sembrare un sistema di cui ti puoi fidare.

Componenti, props e state: il quadro centrale

React diventa più semplice quando smetti di pensare in “schermate” e inizi a pensare in piccoli pezzi. Un componente è un'unità riutilizzabile di UI. Prende input e restituisce una descrizione di come dovrebbe apparire l'interfaccia per quegli input.

Aiuta trattare un componente come una descrizione pura: “dato questo dato, mostra questo”. Quella descrizione può essere usata in molti posti perché non dipende da dove vive.

Le props sono gli input. Vengono da un componente genitore. Le props non sono “possesso” del componente e non sono qualcosa che il componente dovrebbe cambiare di nascosto. Se un pulsante riceve label="Save", il compito del pulsante è renderizzare quell'etichetta, non decidere che dovrebbe essere diversa.

Lo state è dato posseduto. È ciò che il componente ricorda nel tempo. Lo state cambia quando un utente interagisce, quando una richiesta finisce, o quando decidi che qualcosa deve essere diverso. A differenza delle props, lo state appartiene a quel componente (o a qualunque componente tu scelga come proprietario).

Versione semplice dell'idea principale: l'interfaccia è una funzione dello stato. Se lo stato dice “loading”, mostra uno spinner. Se lo stato dice “error”, mostra un messaggio. Se lo stato dice “items = 3”, rendi tre righe. Il tuo compito è fare in modo che l'interfaccia legga dallo stato, non che derivi in variabili nascoste.

Un modo veloce per separare i concetti è:

  • Componente: il pezzo di UI (SearchBox, ProfileCard, CheckoutForm)
  • Props: ciò di cui ha bisogno per mostrare (name, price, disabled)
  • State: ciò che ricorda (isOpen, query, selectedId)

Esempio: una modal. Il genitore può passare title e onClose come props. La modal potrebbe possedere isAnimating come state.

Anche se stai generando UI tramite chat (per esempio su Koder.ai), questa separazione è ancora il modo più veloce per restare sane: decidi prima cosa è props e cosa è state, poi lascia che la UI segua.

Rendering e re-rendering: cosa cambia davvero

Un modo utile per tenere React in testa (molto nello spirito di Dan Abramov) è: il rendering è un calcolo, non un lavoro di pittura. React esegue le tue funzioni di componente per capire come dovrebbe apparire l'interfaccia per le props e lo state correnti. L'output è una descrizione della UI, non pixel.

Un re-render significa solo che React ripete quel calcolo. Non significa “l'intera pagina viene ridisegnata”. React confronta il nuovo risultato con il precedente e applica il minimo insieme di cambiamenti al DOM reale. Molti componenti possono re-renderizzare mentre solo pochi nodi DOM vengono effettivamente aggiornati.

La maggior parte dei re-render avviene per pochi motivi semplici: lo state di un componente è cambiato, le sue props sono cambiate, o un genitore ha re-renderizzato e React ha chiesto al figlio di renderizzare di nuovo. Quest'ultimo sorprende le persone, ma di solito va bene. Se tratti il render come “economico e noioso”, la tua app rimane più facile da ragionare.

La regola empirica che mantiene tutto pulito: rendi il render puro. Dati gli stessi input (props + state), il tuo componente dovrebbe restituire la stessa descrizione UI. Tieni le sorprese fuori dal render.

Esempio concreto: se generi un ID con Math.random() dentro il render, un re-render lo cambia e improvvisamente una checkbox perde il focus o un elemento di lista viene rimontato. Crea l'ID una sola volta (state, memo o fuori dal componente) e il render diventa stabile.

Se ricordi una frase: un re-render significa “ricalcola cosa dovrebbe essere l'interfaccia”, non “ricostruisci tutto”.

Aggiornamenti di stato: tienili piccoli e prevedibili

Un altro modello utile: gli aggiornamenti di stato sono richieste, non assegnazioni istantanee. Quando chiami un setter come setCount(count + 1), stai chiedendo a React di schedulare un render con un nuovo valore. Se leggi lo stato subito dopo, potresti vedere ancora il vecchio valore perché React non ha ancora renderizzato.

Per questo motivo gli aggiornamenti “piccoli e prevedibili” sono importanti. Preferisci descrivere il cambiamento invece di prendere quello che pensi sia il valore corrente. Quando il prossimo valore dipende da quello precedente, usa la forma updater: setCount(c => c + 1). Corrisponde a come funziona React: più aggiornamenti possono essere messi in coda e poi applicati in ordine.

L'immutabilità è l'altra metà del quadro. Non cambiare oggetti e array in posto. Creane uno nuovo con la modifica. React può allora vedere “questo valore è nuovo”, e il tuo cervello può tracciare cosa è cambiato.

Esempio: togglare un todo. L'approccio sicuro è creare un nuovo array e un nuovo oggetto todo per l'elemento che hai cambiato. L'approccio rischioso è invertire todo.done = !todo.done dentro l'array esistente.

Mantieni anche lo stato minimo. Una trappola comune è memorizzare valori che puoi calcolare. Se hai già items e filter, non memorizzare filteredItems nello stato. Calcolalo durante il render. Pochi variabili di stato significa meno modi in cui i valori possono andare fuori sync.

Un test semplice per capire cosa appartiene allo stato:

  • Conservalo se può cambiare nel tempo e l'interfaccia deve reagire.
  • Non memorizzarlo se può essere derivato da altri state o props.
  • Non memorizzare duplicati della stessa verità in due posti.

Se stai costruendo UI via chat (incluso su Koder.ai), chiedi cambiamenti come patch piccole: “Aggiungi un booleano” o “Aggiorna questa lista in modo immutabile.” Cambiamenti piccoli ed espliciti mantengono il generatore e il tuo codice React allineati.

Gli effetti servono per sincronizzare, non per la logica UI di base

Usa la Modalità Pianificazione
Pianifica proprietari di stato e flusso dei dati prima del codice, così i rerender smettono di sembrare casuali.

Il rendering descrive l'interfaccia. Gli effetti sincronizzano con il mondo esterno. “Esterno” significa cose che React non controlla: chiamate di rete, timer, API del browser e talvolta lavoro DOM imperativo.

Se qualcosa può essere calcolato da props e state, di solito non dovrebbe vivere in un effect. Metterlo in un effect aggiunge un secondo passaggio (render, esegui effect, setta state, render di nuovo). Quel salto extra è dove compaiono flicker, loop e bug del tipo “perché questo è obsoleto?”.

Una confusione comune: hai firstName e lastName, e memorizzi fullName nello stato usando un effect. Ma fullName non è un effetto secondario. È dato derivato. Calcolalo durante il render e sarà sempre coerente.

Per abitudine: deriva i valori UI durante il render (o con useMemo quando è davvero costoso), e usa gli effect per “fare qualcosa”, non per “capire qualcosa”.

L'array delle dipendenze è una lista di trigger

Tratta l'array delle dipendenze come: “Quando questi valori cambiano, risincronizza col mondo esterno.” Non è un trucco di performance e non è un posto per zittire i warning.

Esempio: se recuperi dettagli utente quando cambia userId, userId appartiene all'array delle dipendenze perché dovrebbe attivare la sincronizzazione. Se l'effect usa anche token, includilo, altrimenti potresti fare la fetch con un token vecchio.

Un buon controllo istintivo: se rimuovere un effect renderebbe solo sbagliata l'interfaccia, probabilmente non era un vero effect. Se rimuovendolo si interrompe un timer, si cancella una subscription, o si salta una fetch, probabilmente lo era.

Flusso dei dati: una fonte di verità è meglio di stato sparso

Uno dei modelli mentali più utili è semplice: i dati scorrono verso il basso nell'albero, e le azioni utente risalgono.

Un genitore passa valori ai figli. I figli non dovrebbero “possedere” segretamente lo stesso valore in due posti. Richiedono cambiamenti chiamando una funzione, e il genitore decide quale sarà il nuovo valore.

Quando due parti dell'interfaccia devono essere d'accordo, scegli un posto unico per conservare il valore e poi passalo giù. Questo è il “lifting state”. Può sembrare tubature in più, ma previene un problema peggiore: due stati che divergono e ti costringono ad aggiungere hack per tenerli sincronizzati.

Esempio: una search box e una lista di risultati. Se l'input memorizza la sua query e la lista memorizza la sua query, alla fine vedrai “l'input mostra X ma la lista usa Y.” La soluzione è tenere query in un genitore, passarlo a entrambi e passare un handler onChangeQuery(newValue) all'input.

Sollevarlo non è sempre la risposta. Se un valore conta solo dentro un componente, tienilo lì. Tenere lo stato vicino al suo uso spesso rende il codice più leggibile.

Una barriera pratica:

  • Stato locale: dettagli solo UI (dropdown aperto, quale tab è evidenziata)
  • Stato condiviso: dati che più componenti devono concordare (filtri, elemento selezionato, stato auth)

Se non sei sicuro se sollevare lo stato, cerca segnali come: due componenti mostrano lo stesso valore in modi diversi; un'azione in un posto deve aggiornare qualcosa lontano; copi continuamente props nello stato “per sicurezza”; o aggiungi effect solo per mantenere due valori allineati.

Questo modello aiuta anche quando costruisci con strumenti di chat come Koder.ai: chiedi un unico proprietario per ogni pezzo di stato condiviso, poi genera handler che fluiscono verso l'alto.

Passo dopo passo: progetta una feature disegnando prima lo stato

Scegli una feature abbastanza piccola da tenere in testa. Una buona è una lista ricercabile dove puoi cliccare un elemento per vedere i dettagli in una modal.

Inizia schizzando le parti dell'interfaccia e gli eventi che possono succedere. Non pensare al codice ancora. Pensa a cosa può fare l'utente e cosa può vedere: c'è un input di ricerca, una lista, una riga selezionata evidenziata e una modal. Gli eventi sono digitare nella ricerca, cliccare un elemento, aprire la modal e chiudere la modal.

Ora “disegna lo stato”. Scrivi i pochi valori che devono essere conservati e decidi chi li possiede. Una regola semplice funziona: il genitore comune più vicino di tutti i posti che hanno bisogno di un valore dovrebbe possederlo.

Per questa feature, lo stato memorizzato può essere minimo: query (stringa), selectedId (id o null) e isModalOpen (booleano). La lista legge query e rende gli elementi. La modal legge selectedId per mostrare i dettagli. Se sia lista che modal hanno bisogno di selectedId, tienilo nel genitore, non in entrambi.

Poi separa i dati derivati da quelli memorizzati. La lista filtrata è derivata: filteredItems = items.filter(...). Non memorizzarla nello stato perché può sempre essere ricalcolata da items e query. Memorizzare dati derivati è il modo in cui i valori divergono.

Solo dopo chiediti: abbiamo bisogno di un effect? Se gli items sono già in memoria, no. Se digitare una query deve fare fetch, sì. Se chiudere la modal deve salvare qualcosa, sì. Gli effect servono per sincronizzare (fetch, save, subscribe), non per wiring UI di base.

Infine, testa il flusso con qualche caso limite:

  • Digita veloce, poi cancella: la lista torna normale?
  • Clicchi un elemento, poi la lista cambia per via della ricerca: selectedId è ancora valido?
  • Chiudi la modal: mantieni la selezione o la resetti?
  • Gli items si aggiornano dal server: cosa succede se l'elemento selezionato sparisce?

Se puoi rispondere a queste su carta, il codice React è di solito diretto.

Errori comuni che rompono il modello mentale

Aggiungi un backend reale
Crea un'API Go con PostgreSQL insieme alla tua UI React nello stesso flusso di lavoro.

La maggior parte della confusione in React non riguarda la sintassi. Succede quando il tuo codice smette di corrispondere alla semplice storia che hai in testa.

Pattern di errore (e il sintomo che noti)

Memorizzare stato derivato. Salvi fullName nello stato anche se è solo firstName + lastName. Funziona finché un campo cambia e l'altro no, e l'interfaccia mostra un valore obsoleto.

Loop di effect. Un effect fa fetch, setta lo stato e la lista di dipendenze lo fa girare di nuovo. Il sintomo è richieste ripetute, UI tremolante o stato che non si stabilizza.

Closure obsoleta. Un handler di clic legge un valore vecchio (come un contatore o un filtro non aggiornato). Il sintomo è “ho cliccato, ma ha usato il valore di ieri”.

Stato globale ovunque. Mettere ogni dettaglio UI in uno store globale rende difficile capire chi possiede cosa. Il sintomo è che cambiando una cosa tre schermate reagiscono in modi sorprendenti.

Mutare oggetti annidati. Aggiorni un oggetto o un array in posto e ti chiedi perché l'interfaccia non si è aggiornata. Il sintomo è “i dati sono cambiati, ma niente ha re-renderizzato”.

Ecco un esempio concreto: un pannello “search and sort” per una lista. Se memorizzi filteredItems nello stato, può divergere da items quando arrivano nuovi dati. Invece, memorizza gli input (testo di ricerca, scelta sort) e calcola la lista filtrata durante il render.

Con gli effect, tienili per sincronizzarti col mondo esterno (fetching, subscription, timer). Se un effect sta facendo lavoro UI di base, spesso appartiene al render o a un handler di evento.

Quando generi o modifichi codice via chat, questi errori emergono più rapidamente perché le modifiche possono arrivare a blocchi. Una buona abitudine è inquadrare le richieste in termini di proprietà: “Qual è la fonte di verità per questo valore?” e “Possiamo calcolarlo invece di memorizzarlo?”.

Checklist rapida quando lo stato inizia a sentirsi caotico

Quando la tua UI inizia a sembrare imprevedibile, raramente è “troppo React.” Di solito è troppo stato, nei posti sbagliati, che fa lavori che non dovrebbe fare.

Prima di aggiungere un altro useState, fermati e chiediti:

  • Qual è l'insieme più piccolo di valori che descrive completamente ciò che l'utente vede in questo momento?
  • Dove dovrebbe vivere ogni valore così le parti che ne hanno bisogno possono leggerlo, e le parti che non ne hanno bisogno restano calme?
  • Questo valore è davvero nuova informazione, o puoi calcolarlo da props, parametri URL o altri state?
  • Stai usando un effect per calcolare UI, o solo per sincronizzarti con qualcosa fuori React (network, timer, API del browser)?
  • Se un valore cambia, quali componenti dovrebbero re-renderizzare perché lo leggono, e quali no?

Esempio piccolo: search box, dropdown filtro, lista. Se memorizzi sia query che filteredItems nello stato, ora hai due fonti di verità. Invece, tieni query e filter come stato, poi deriva filteredItems durante il render dall'intera lista.

Questo conta anche quando costruisci rapidamente con strumenti chat. La velocità è fantastica, ma continua a chiedere: “Abbiamo aggiunto stato, o abbiamo aggiunto un valore derivato per errore?” Se è derivato, elimina quello stato e calcolalo.

Esempio: iterare su una UI rapidamente senza perdere prevedibilità

Refattorizza senza paura
Fai uno snapshot prima di una refactor, poi torna indietro se la storia dello stato peggiora.

Un piccolo team costruisce una UI admin: una tabella di ordini, qualche filtro e un dialog per modificare un ordine. La prima richiesta è vaga: “Aggiungi filtri e un popup di modifica.” Sembra semplice, ma spesso si trasforma in stato sparso dappertutto.

Rendilo concreto traducendo la richiesta in stato ed eventi. Invece di “filtri”, nomina lo stato: query, status, dateRange. Invece di “popup di modifica”, nomina l'evento: “l'utente clicca Edit su una riga.” Poi decidi chi possiede ogni pezzo di stato (pagina, tabella o dialog) e cosa può essere derivato (come la lista filtrata).

Esempi di prompt che mantengono intatto il modello (funzionano bene anche in builder chat come Koder.ai):

  • “Crea una OrdersPage che possiede filters e selectedOrderId. OrdersTable è controllata da filters e chiama onEdit(orderId).”
  • “Deriva visibleOrders da orders e filters. Non memorizzare visibleOrders nello stato.”
  • “Aggiungi un EditOrderDialog che riceve order e open. Quando salvi, chiama onSave(updatedOrder) e chiudi.”
  • “Se i dati vengono fetchati, tienili in un solo posto. Usa un effect solo per sincronizzare filters con l'URL, non per calcolare le righe filtrate.”

Dopo che l'interfaccia è generata o aggiornata, rivedi i cambiamenti con un controllo rapido: ogni valore di stato ha un solo proprietario, i valori derivati non sono salvati, gli effect servono solo per sincronizzare con il mondo esterno (URL, network, storage), e gli eventi fluiscono giù come props e su come callback.

Quando lo stato è prevedibile, iterare diventa sicuro. Puoi cambiare il layout della tabella, aggiungere un nuovo filtro o modificare i campi del dialog senza indovinare quale stato nascosto si romperà dopo.

Passi successivi: costruisci più in fretta con la chat, ma mantieni il modello React

La velocità è utile solo se l'app resta facile da ragionare. La protezione più semplice è trattare questi modelli mentali come una checklist da applicare prima di scrivere (o generare) UI.

Inizia ogni feature allo stesso modo: scrivi lo stato necessario, gli eventi che possono cambiarlo e chi lo possiede. Se non riesci a dire, “Questo componente possiede questo stato, e questi eventi lo aggiornano,” probabilmente finirai con stato sparso e re-render sorprendenti.

Se costruisci tramite chat, inizia in modalità pianificazione. Descrivi i componenti, la forma dello stato e le transizioni in linguaggio semplice prima di chiedere codice. Per esempio: “Un pannello filtri aggiorna lo stato query; la lista dei risultati è derivata da query; selezionare un elemento imposta selectedId; chiudere pulisce la selezione.” Quando questo suona chiaro, generare la UI diventa un passo meccanico.

Se usi Koder.ai (Koder.ai) per generare codice React, vale la pena fare un rapido controllo di sanità prima di procedere: un proprietario chiaro per ogni valore di stato, UI derivata dallo stato, effect solo per sincronizzare, e nessuna fonte duplicata di verità.

Poi itera a piccoli passi. Se vuoi cambiare la struttura dello stato (per esempio da diversi booleani a un singolo campo status), fai prima uno snapshot, sperimenta e torna indietro se il modello mentale peggiora. E quando serve una revisione più profonda o un handoff, esportare il codice sorgente rende più facile rispondere alla domanda reale: la forma dello stato racconta ancora la storia dell'interfaccia?

Domande frequenti

Qual è il modello mentale più semplice per rendere React prevedibile?

Un buon modello di partenza è: UI = f(state, props). I tuoi componenti non "modificano il DOM"; descrivono cosa dovrebbe esserci sullo schermo per i dati attuali. Se lo schermo è sbagliato, guarda lo state/props che l'hanno prodotto, non il DOM.

Come decido se qualcosa deve essere props o state?

Le props sono input da un genitore; il tuo componente dovrebbe considerarle in sola lettura. Lo state è memoria posseduta da un componente (o dal componente che scegli come proprietario). Se un valore deve essere condiviso, sollevalo verso l'alto e passalo come props.

Quando React "re-renderizza", sta ricostruendo tutta la pagina?

Un re-render significa che React riesegue la funzione del tuo componente per calcolare la prossima descrizione UI. Non significa automaticamente che l'intera pagina viene ridipinta. React poi aggiorna il DOM reale con il minimo insieme di modifiche necessario.

Perché il mio stato sembra "un passo indietro" dopo aver chiamato setState?

Perché gli aggiornamenti di stato sono programmati, non assegnazioni immediate. Se il prossimo valore dipende da quello precedente, usa la forma updater così non fai affidamento su un valore potenzialmente obsoleto:

  • setCount(c => c + 1)

Così rimane corretto anche se più aggiornamenti sono in coda.

Come evito i bug da "derived state"?

Evita di salvare tutto ciò che puoi calcolare da input esistenti. Conserva gli input e deriva il resto durante il render.

Esempi:

  • Memorizza: items, filter
  • Deriva: visibleItems = items.filter(...)

Questo evita che i valori si sfasino.

Quando dovrei usare useEffect e quando evitarlo?

Usa gli effect per sincronizzarti con cose che React non controlla: fetch, subscription, timer, API del browser o lavoro DOM imperativo.

Non usare un effect solo per calcolare valori UI dallo state—calcola quelli durante il render (o con useMemo se è costoso).

Qual è il modo corretto di pensare all'array delle dipendenze di useEffect?

Tratta le dipendenze come una lista di trigger: “quando questi valori cambiano, risincronizza”. Includi ogni valore reattivo che l'effect legge.

Se lasci fuori qualcosa rischi dati obsoleti (es. un userId o token vecchio). Se aggiungi cose sbagliate puoi creare loop—spesso segno che l'effect sta facendo lavoro che appartiene ad eventi o al render.

Come so quando sollevare lo stato?

Se due parti dell'interfaccia devono sempre essere allineate, metti lo state nel closest common parent, passalo verso il basso e fornisci callback verso l'alto.

Un test rapido: se duplichi lo stesso valore in due componenti e scrivi effect per tenerli sincronizzati, quello stato probabilmente ha bisogno di un unico proprietario.

Perché i miei handler a volte usano valori obsoleti?

Succede quando un handler “cattura” un vecchio valore da un render precedente. Fix comuni:

  • Usa setter updater: setX(prev => ...)
  • Metti i valori che cambiano nello state/refs, non in variabili normali
  • Assicurati che gli effect elenchino i valori che leggono

Se un click usa “il valore di ieri”, sospetta una closure obsoleta.

Come posso costruire UI React velocemente via chat senza creare stato disordinato?

Comincia con un piccolo piano: componenti, proprietari di stato ed eventi. Poi genera codice come piccole patch (aggiungi un campo di stato, un handler, deriva un valore) invece di riscritture grandi.

Se usi un builder chat come Koder.ai, chiedi per:

  • un proprietario chiaro per ogni valore di stato
  • valori derivati calcolati in render
  • effect solo per sincronizzare (fetch/URL/storage), non per wiring UI di base

Questo mantiene il codice generato allineato al modello mentale di React.

Related posts