Il ciclo di vita di un componente React ha tre fasi: il montaggio (il componente compare sullo schermo per la prima volta), l'aggiornamento (esegue un nuovo render perché sono cambiati il suo stato, le props o il context) e lo smontaggio (viene rimosso). Nei function component reagisci a queste fasi con useEffect: un effetto con [] viene eseguito dopo il montaggio, un effetto con dipendenze viene eseguito dopo gli aggiornamenti in cui sono cambiate, e la funzione restituita da un effetto viene eseguita allo smontaggio.
La console sotto l'anteprima mostra ogni fase mentre avviene.
La console inizia con render, count = 0 e mounted. Ogni clic su Count registra un rendering e un aggiornamento. Unmount registra unmounted; Mount again riparte da count = 0, perché lo stato è stato buttato via insieme al componente.
Montaggio
Un componente viene montato la prima volta che React lo renderizza in un certo punto dell'albero. React chiama la funzione, crea i nodi DOM dal JSX che restituisce, li inserisce, lascia che il browser disegni la pagina e poi esegue gli effetti del componente.
useEffect(fn, []) è dove avvii le cose che devono esistere per tutto il tempo in cui il componente è sullo schermo: una sottoscrizione, un timer, una connessione, una prima richiesta di dati. Viene eseguito dopo il disegno, quindi non ritarda mai il primo frame.
Aggiornamento
Un componente si aggiorna (esegue un nuovo render) quando:
- cambia il suo stato,
- il suo genitore si renderizza di nuovo (per default, si renderizza anche ogni figlio, che le sue props siano cambiate o no),
- cambia un context che legge.
Dopo ogni aggiornamento, React esegue gli effetti le cui dipendenze sono cambiate. Prima di eseguire di nuovo un effetto, esegue la funzione di cleanup precedente di quell'effetto, così la vecchia sottoscrizione viene chiusa prima che si apra quella nuova. La pagina sul rendering spiega più in dettaglio cosa provoca un rendering.
Smontaggio
Un componente viene smontato quando il suo genitore smette di renderizzarlo (come fa {show && <Counter />} qui sopra), quando cambia la sua key, o quando viene smontata la root stessa. React rimuove i suoi nodi DOM, esegue la funzione di cleanup di ogni effetto e butta via il suo stato. Se il componente ricompare più tardi, è un'istanza completamente nuova.
Genitori e figli
React renderizza dall'alto verso il basso ma esegue gli effetti dal basso verso l'alto. Quando un genitore e un figlio vengono montati insieme, gli effetti del figlio vengono eseguiti per primi, così l'effetto di un genitore può contare sul fatto che i suoi figli siano completamente pronti.
Il montaggio registra render Parent, render Child, Child effect, Parent effect. Premi Remove e le funzioni di cleanup vengono eseguite nell'ordine inverso: Parent cleanup, poi Child cleanup.
Fasi di render e commit
Ogni aggiornamento passa per due fasi:
- Fase di render. React chiama i tuoi componenti per calcolare il nuovo JSX e lo confronta con il risultato precedente. Sullo schermo non cambia ancora nulla. React può mettere in pausa questo lavoro, eseguirlo due volte in StrictMode o buttarlo via, ed è per questo che il rendering deve essere puro: niente richieste, niente sottoscrizioni, niente modifiche a variabili esterne.
- Fase di commit. React applica le differenze al DOM, collega i ref ed esegue le callback di
useLayoutEffect, tutto prima che il browser disegni la pagina. Le callback diuseEffectvengono eseguite dopo, di solito una volta che il browser ha disegnato la pagina. Quando l'aggiornamento è stato causato da un clic o da un altro input discreto, React le esegue invece subito dopo i layout effect, così il risultato è pronto prima dell'evento successivo.
Ogni clic registra le tre righe nello stesso ordine. Questi effetti non hanno un array delle dipendenze, quindi vengono eseguiti dopo ogni rendering. Aggiungi [] come secondo argomento a entrambi e vengono eseguiti solo dopo il primo.
I metodi del ciclo di vita delle classi e i loro equivalenti con gli hook
I class component esprimono il ciclo di vita con metodi dal nome preciso. Li incontrerai nel codice più vecchio e negli error boundary, che richiedono ancora una classe.
| Metodo della classe | Quando viene eseguito | Equivalente nei function component |
|---|---|---|
constructor | una volta, prima del primo rendering | useState(initial), useRef |
static getDerivedStateFromProps | prima di ogni rendering | calcola il valore durante il rendering, o azzera con una key |
render | a ogni rendering | il corpo della funzione |
componentDidMount | dopo che il primo rendering è nel DOM | useEffect(fn, []) |
shouldComponentUpdate | prima di un aggiornamento, può saltarlo | React.memo (restituisce la risposta opposta: "le props sono uguali") |
getSnapshotBeforeUpdate | subito prima che il DOM cambi | nessun hook diretto; useLayoutEffect con un ref è il più vicino |
componentDidUpdate | dopo ogni aggiornamento | useEffect(fn, [deps]) (viene eseguito anche dopo il montaggio) |
componentWillUnmount | prima della rimozione | la funzione di cleanup restituita da useEffect |
getDerivedStateFromError, componentDidCatch | quando un figlio genera un errore | nessuno: scrivi una classe error boundary |
Ecco una classe che registra ogni metodo mentre viene eseguito:
Il montaggio registra constructor, getDerivedStateFromProps, render, componentDidMount. Un clic registra getDerivedStateFromProps, shouldComponentUpdate, render, getSnapshotBeforeUpdate, componentDidUpdate. Fai restituire false a shouldComponentUpdate e i clic smettono di aggiornare il pulsante: lo stato cambia, ma React salta il rendering. La pagina sui class component mostra come convertire una classe come questa in una funzione.
Ragiona in termini di sincronizzazione, non di ciclo di vita
La tabella degli hook è utile per leggere il codice vecchio, ma può portarti a scrivere effetti che copiano le abitudini delle classi, come un unico grande effetto "did mount" e un unico grande effetto "did update". Un effetto si legge meglio come "mantieni questa cosa esterna sincronizzata con questi valori": connettiti a roomId, e quando roomId cambia, disconnettiti e riconnettiti. Scrivi un effetto per ogni cosa che sincronizzi, elenca come dipendenze i valori che usa e lascia che sia React a decidere quando eseguirlo.
Domande frequenti
Quali sono le fasi del ciclo di vita di un componente React?
Il montaggio (React crea il componente e mette il suo output sullo schermo), l'aggiornamento (esegue un nuovo render perché sono cambiati il suo stato, le props o il context) e lo smontaggio (viene rimosso ed esegue la pulizia).
Qual è l'equivalente con gli hook di componentDidMount?
useEffect(() => { ... }, []). L'array delle dipendenze vuoto lo fa eseguire una volta, dopo che il primo rendering è sullo schermo. La funzione che restituisce è l'equivalente di componentWillUnmount.
Qual è l'equivalente con gli hook di componentDidUpdate?
useEffect con i valori che ti interessano nell'array delle dipendenze, come [userId]. A differenza di componentDidUpdate, viene eseguito anche dopo il primo rendering, quindi copre insieme montaggio e aggiornamento.
In che ordine vengono eseguiti gli effetti di genitore e figlio?
React renderizza dal genitore verso il basso, ma esegue gli effetti dai figli verso l'alto: l'effetto di un figlio viene eseguito prima di quello del genitore. Allo smontaggio, la funzione di cleanup del genitore viene eseguita prima di quella del figlio.
Cosa sono le fasi di render e commit?
Nella fase di render React chiama i tuoi componenti per capire come deve apparire lo schermo; può mettere in pausa questo lavoro o buttarlo via. Nella fase di commit applica le modifiche al DOM ed esegue i layout effect; gli effetti normali vengono eseguiti dopo, di solito una volta che il browser ha disegnato la pagina.