Menu

useLayoutEffect in React: misurare prima del disegno

useLayoutEffect funziona come useEffect ma viene eseguito subito dopo che React aggiorna il DOM e prima che il browser disegni la pagina. Usalo per misurare un elemento e sistemare il layout senza uno sfarfallio visibile, e tieni useEffect per tutto il resto.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

useLayoutEffect è una versione di useEffect che viene eseguita prima: subito dopo che React ha aggiornato il DOM, ma prima che il browser disegni il nuovo frame. Il codice al suo interno può misurare elementi e cambiare il layout, e l'utente vede solo il risultato finale. Blocca il disegno mentre è in esecuzione, quindi usalo solo quando ti serve quel momento preciso.

Gli effetti sono dichiarati nell'ordine effect, layout effect, ma la console stampa per primo il layout effect a ogni rendering. Tra i due hook non conta l'ordine di dichiarazione; contano i loro tempi.

La sintassi

La firma è identica a quella di useEffect: una funzione di setup, una funzione di cleanup facoltativa e un array delle dipendenze.

useLayoutEffect(() => {
    // runs after the DOM update, before paint
    return () => {
        // cleanup, before the next run and on unmount
    };
}, [dependencies]);

Tutto ciò che sai su dipendenze e cleanup da useEffect vale anche qui. L'unica differenza è quando React lo chiama.

Quando viene eseguito ciascuno

Dopo un rendering, React aggiorna il DOM e poi fa tre cose in un ordine fisso:

  1. Esegue tutte le funzioni di cleanup dei layout effect e i layout effect, in modo sincrono.
  2. Lascia che il browser disegni la pagina. Se un layout effect ha impostato lo stato, React prima esegue un nuovo render, sempre prima del disegno.
  3. Esegue gli effetti normali, di solito dopo il disegno.

In un albero, ogni layout effect viene eseguito prima di qualsiasi effetto normale, e i figli vengono eseguiti prima dei genitori, perché un genitore può terminare solo quando i suoi figli sono al loro posto:

La console mostra prima entrambi i layout effect, Child e poi Parent, poi entrambi gli effetti normali nello stesso ordine. Il key={n} fa sì che ogni clic monti un Parent nuovo, quindi ottieni ogni volta le stesse quattro righe. Aggiungi a ogni effetto una funzione di cleanup che registra un log e guarda quelle vecchie essere eseguite prima delle nuove.

Il problema dello sfarfallio

Immagina un tooltip che deve comparire sopra il suo pulsante, a meno che sopra non ci sia spazio, nel qual caso va sotto. Non puoi sapere se c'è spazio finché il tooltip non è nel DOM e puoi misurarne l'altezza. Quindi il rendering avviene in due passaggi:

  1. Renderizza il tooltip da qualche parte, così da poterlo misurare.
  2. Misuralo, calcola la posizione reale, imposta lo stato, renderizza di nuovo.

Con useEffect, il browser può disegnare la pagina tra quei due passaggi, e l'utente vede il tooltip saltare dalla prima posizione alla seconda. Con useLayoutEffect, il secondo passaggio avviene prima del disegno, quindi la prima posizione non viene mai mostrata.

Passa il mouse sul pulsante in alto: sopra non c'è spazio, quindi il tooltip compare sotto. Passa sopra quello in basso e compare sopra. La console ti dice quale lato ha scelto la misurazione. Cambia useLayoutEffect in useEffect e passa di nuovo il mouse: ora il tooltip può lampeggiare in cima al riquadro per un frame prima di spostarsi. Su una macchina veloce spesso è troppo rapido per notarlo, ed è proprio per questo che questo bug arriva in produzione e poi viene segnalato da telefoni più lenti.

offsetHeight, offsetTop e getBoundingClientRect() costringono tutti il browser a calcolare il layout, quindi leggerli nel layout effect ti dà i valori reali del DOM che React ha appena scritto.

Misurare un elemento

Lo stesso schema funziona per qualsiasi dimensione da cui dipende il rendering: leggila con un ref in un layout effect, salvala nello stato, renderizza con essa.

Clicca il pulsante e la riga della larghezza si aggiorna nello stesso frame del testo. La pagina non mostra mai un testo nuovo accanto a una larghezza vecchia, perché il secondo rendering avviene prima del disegno. Togli [text] dall'array delle dipendenze e la misurazione viene eseguita dopo ogni rendering, compreso quello provocato dal suo stesso setWidth; si ferma lì solo perché la larghezza non cambia più.

Preferisci useEffect

Un layout effect ritarda il disegno finché non restituisce, e se imposta lo stato, anche finché non termina il rendering in più. Uno lento fa sembrare lento ogni aggiornamento della pagina. Usa useLayoutEffect solo quando sono vere tutte queste condizioni:

  • L'effetto legge il layout (dimensione, posizione, scorrimento).
  • Cambia ciò che viene renderizzato in base a quella lettura.
  • Lo stato intermedio sarebbe visibile e apparirebbe sbagliato.

Tutto il resto (recupero dei dati, sottoscrizioni, timer, log, analytics) va in useEffect. Una regola veloce: parti con useEffect, e passa all'altro solo se vedi uno sfarfallio.

Di solito useEffect viene eseguito dopo il disegno, ma non sempre. Quando l'aggiornamento arriva da un'azione discreta dell'utente come un clic o la pressione di un tasto, React può eseguire gli effetti prima che il browser disegni la pagina. È uno dei motivi per cui uno sfarfallio può comparire in una situazione e non in un'altra.

Rendering lato server

Gli effetti non vengono mai eseguiti durante il rendering lato server, e nemmeno useLayoutEffect. Il server invia l'HTML del primo rendering, e il layout effect viene eseguito solo dopo l'hydration nel browser. React 18 e le versioni precedenti avvisavano con "useLayoutEffect does nothing on the server" quando un componente del genere veniva renderizzato sul server; React 19 ha eliminato quell'avviso.

Se il primo frame renderizzato dal server apparirebbe sbagliato senza la misurazione, ci sono due soluzioni comuni: renderizzare un fallback neutro (per esempio, nascondere il tooltip finché non viene misurato), oppure renderizzare il componente solo sul client:

function ClientOnly({ children }) {
    const [mounted, setMounted] = useState(false);
    useEffect(() => setMounted(true), []);
    return mounted ? children : null;
}

useInsertionEffect

Esiste un terzo hook per gli effetti, useInsertionEffect, che viene eseguito prima di qualsiasi layout effect. Esiste per le librerie CSS in JS che generano tag <style> a runtime: inserire lì gli stili significa che un layout effect che misura un elemento lo vede già con il suo stile. Non può aggiornare lo stato e i ref non sono ancora collegati, quindi non serve a nulla nel codice applicativo. Se non stai scrivendo una libreria di stili, non ti servirà.

Domande frequenti

Qual è la differenza tra useEffect e useLayoutEffect?

I tempi. useLayoutEffect viene eseguito in modo sincrono dopo che React ha cambiato il DOM ma prima che il browser disegni la pagina, quindi i suoi aggiornamenti di stato vengono applicati prima che l'utente veda qualcosa. useEffect di solito viene eseguito dopo il disegno. L'API e l'array delle dipendenze sono gli stessi.

Quando dovrei usare useLayoutEffect?

Quando un effetto legge il layout (una dimensione o una posizione) e cambia subito ciò che viene renderizzato in base a esso, come posizionare un tooltip sopra o sotto il suo pulsante. Senza, l'utente può vedere per un frame la prima posizione, quella sbagliata.

useLayoutEffect fa male alle prestazioni?

Impedisce al browser di disegnare la pagina finché non termina, insieme a qualsiasi nuovo render che provoca. Un layout effect lento fa sembrare lenta l'intera pagina, quindi usa useEffect a meno che tu non debba evitare uno sfarfallio visibile.

useLayoutEffect viene eseguito sul server?

No. Nessuno dei due effetti viene eseguito durante il rendering lato server. Le versioni più vecchie di React stampavano un avviso quando un componente con useLayoutEffect veniva renderizzato sul server; React 19 non lo fa più, ma l'effetto viene comunque eseguito solo nel browser.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA