useLayoutEffect
useLayoutEffect è una versione di useEffect che viene eseguita prima che il browser ridipinge lo schermo.
useLayoutEffect(setup, dependencies?)Reference
useLayoutEffect(setup, dependencies?)
Chiama useLayoutEffect per eseguire le misurazioni di layout prima che il browser ridipinge lo schermo:
import { useState, useRef, useLayoutEffect } from 'react';
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0);
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height);
}, []);
// ...Parameters
-
setup: La funzione con la logica del tuo Effetto. La funzione di setup può anche restituire opzionalmente una funzione di cleanup. Dopo che il tuo componente esegue la fase di commit nel DOM e prima che il browser ridipinge lo schermo, React eseguirà la tua funzione di setup. Dopo ogni fase di commit con dipendenze cambiate, React eseguirà prima la funzione di cleanup (se l’hai fornita) con i valori precedenti, e poi eseguirà la funzione di setup con i nuovi valori. Prima che il tuo componente venga rimosso dal DOM, React eseguirà la funzione di cleanup. -
optional
dependencies: L’elenco di tutti i valori reattivi referenziati all’interno del codice disetup. I valori reattivi includono props, state e tutte le variabili e funzioni dichiarate direttamente nel corpo del componente. Se il tuo linter è configurato per React, verificherà che ogni valore reattivo sia specificato correttamente come dipendenza. L’elenco delle dipendenze deve avere un numero costante di elementi ed essere scritto inline come[dep1, dep2, dep3]. React confronterà ogni dipendenza con il suo valore precedente usando il confrontoObject.is. Se ometti questo argomento, il tuo Effetto verrà rieseguito dopo ogni fase di commit del componente.
Returns
useLayoutEffect restituisce undefined.
Caveats
-
useLayoutEffectè un Hook, quindi puoi chiamarlo solo al top level del tuo componente o dei tuoi Hook. Non puoi chiamarlo all’interno di loop o condizioni. Se ne hai bisogno, estrai un componente e sposta l’Effetto lì. -
Quando Strict Mode è attivo, React eseguirà un ciclo setup+cleanup extra solo in development prima del primo setup reale. È un test di stress che verifica che la logica di cleanup “specchi” la logica di setup e che fermi o annulli ciò che fa il setup. Se questo causa un problema, implementa la funzione di cleanup.
-
Se alcune delle tue dipendenze sono oggetti o funzioni definite all’interno del componente, c’è il rischio che facciano rieseguire l’Effetto più spesso del necessario. Per risolvere, rimuovi le dipendenze oggetto e funzione non necessarie. Puoi anche estrarre gli aggiornamenti di state e la logica non reattiva fuori dall’Effetto.
-
Gli Effetti vengono eseguiti solo sul client. Non vengono eseguiti durante la renderizzazione lato server.
-
Il codice all’interno di
useLayoutEffecte tutti gli aggiornamenti di state programmati da esso impediscono al browser di ridipingere lo schermo. Se usato eccessivamente, rende l’app lenta. Quando possibile, preferisciuseEffect. -
Se attivi un aggiornamento di state all’interno di
useLayoutEffect, React eseguirà immediatamente tutti gli Effetti rimanenti, inclusouseEffect.
Usage
Misurare il layout prima che il browser ridipinge lo schermo
La maggior parte dei componenti non ha bisogno di conoscere la propria posizione e dimensione sullo schermo per decidere cosa renderizzare. Restituiscono solo del JSX. Poi il browser calcola il loro layout (posizione e dimensione) e ridipinge lo schermo.
A volte non basta. Immagina un tooltip che appare accanto a un elemento al passaggio del mouse. Se c’è abbastanza spazio, il tooltip dovrebbe apparire sopra l’elemento, ma se non entra, dovrebbe apparire sotto. Per renderizzare il tooltip nella posizione finale corretta, devi conoscere la sua altezza (cioè se entra in alto).
Per farlo, devi renderizzare in due passaggi:
- Renderizza il tooltip ovunque (anche con una posizione sbagliata).
- Misura la sua altezza e decidi dove posizionare il tooltip.
- Renderizza il tooltip di nuovo nel posto giusto.
Tutto questo deve avvenire prima che il browser ridipinge lo schermo. Non vuoi che l’utente veda il tooltip spostarsi. Chiama useLayoutEffect per eseguire le misurazioni di layout prima che il browser ridipinge lo schermo:
function Tooltip() {
const ref = useRef(null);
const [tooltipHeight, setTooltipHeight] = useState(0); // Non conosci ancora l'altezza reale
useLayoutEffect(() => {
const { height } = ref.current.getBoundingClientRect();
setTooltipHeight(height); // Ri-renderizza ora che conosci l'altezza reale
}, []);
// ...usa tooltipHeight nella logica di renderizzazione qui sotto...
}Ecco come funziona passo per passo:
Tooltiprenderizza contooltipHeight = 0iniziale (quindi il tooltip potrebbe essere posizionato in modo errato).- React lo posiziona nel DOM ed esegue il codice in
useLayoutEffect. - Il tuo
useLayoutEffectmisura l’altezza del contenuto del tooltip e attiva una ri-renderizzazione immediata. Tooltiprenderizza di nuovo con latooltipHeightreale (quindi il tooltip è posizionato correttamente).- React lo aggiorna nel DOM e il browser infine visualizza il tooltip.
Passa il mouse sui pulsanti qui sotto e osserva come il tooltip regola la sua posizione a seconda che entri o meno:
import { useRef, useLayoutEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import TooltipContainer from './TooltipContainer.js'; export default function Tooltip({ children, targetRect }) { const ref = useRef(null); const [tooltipHeight, setTooltipHeight] = useState(0); useLayoutEffect(() => { const { height } = ref.current.getBoundingClientRect(); setTooltipHeight(height); console.log('Altezza tooltip misurata: ' + height); }, []); let tooltipX = 0; let tooltipY = 0; if (targetRect !== null) { tooltipX = targetRect.left; tooltipY = targetRect.top - tooltipHeight; if (tooltipY < 0) { // Non entra sopra, quindi posizionalo sotto. tooltipY = targetRect.bottom; } } return createPortal( <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}> {children} </TooltipContainer>, document.body ); }
Nota che anche se il componente Tooltip deve renderizzare in due passaggi (prima con tooltipHeight inizializzato a 0 e poi con l’altezza misurata reale), vedi solo il risultato finale. Ecco perché in questo esempio ti serve useLayoutEffect invece di useEffect. Vediamo la differenza nel dettaglio qui sotto.
Esempio 1 di 2: useLayoutEffect impedisce al browser di ridipingere
React garantisce che il codice all’interno di useLayoutEffect e tutti gli aggiornamenti di state programmati al suo interno verranno elaborati prima che il browser ridipinge lo schermo. Questo ti permette di renderizzare il tooltip, misurarlo e renderizzarlo di nuovo senza che l’utente noti la prima renderizzazione extra. In altre parole, useLayoutEffect impedisce al browser di dipingere.
import { useRef, useLayoutEffect, useState } from 'react'; import { createPortal } from 'react-dom'; import TooltipContainer from './TooltipContainer.js'; export default function Tooltip({ children, targetRect }) { const ref = useRef(null); const [tooltipHeight, setTooltipHeight] = useState(0); useLayoutEffect(() => { const { height } = ref.current.getBoundingClientRect(); setTooltipHeight(height); }, []); let tooltipX = 0; let tooltipY = 0; if (targetRect !== null) { tooltipX = targetRect.left; tooltipY = targetRect.top - tooltipHeight; if (tooltipY < 0) { // Non entra sopra, quindi posizionalo sotto. tooltipY = targetRect.bottom; } } return createPortal( <TooltipContainer x={tooltipX} y={tooltipY} contentRef={ref}> {children} </TooltipContainer>, document.body ); }
Troubleshooting
Sto ricevendo un errore: “useLayoutEffect does nothing on the server”
Lo scopo di useLayoutEffect è permettere al tuo componente di usare le informazioni di layout per la renderizzazione:
- Renderizza il contenuto iniziale.
- Misura il layout prima che il browser ridipinge lo schermo.
- Renderizza il contenuto finale usando le informazioni di layout lette.
Quando tu o il tuo framework usate la renderizzazione lato server, la tua app React renderizza in HTML sul server per la renderizzazione iniziale. Questo ti permette di mostrare l’HTML iniziale prima che il codice JavaScript venga caricato.
Il problema è che sul server non ci sono informazioni di layout.
Nell’esempio precedente, la chiamata a useLayoutEffect nel componente Tooltip gli permette di posizionarsi correttamente (sopra o sotto il contenuto) a seconda dell’altezza del contenuto. Se provassi a renderizzare Tooltip come parte dell’HTML iniziale del server, sarebbe impossibile determinarlo. Sul server non c’è ancora layout! Quindi, anche se lo renderizzassi sul server, la sua posizione “saltarebbe” sul client dopo che il JavaScript viene caricato ed eseguito.
Di solito, i componenti che dipendono dalle informazioni di layout non hanno bisogno di renderizzare sul server comunque. Ad esempio, probabilmente non ha senso mostrare un Tooltip durante la renderizzazione iniziale. Viene attivato da un’interazione sul client.
Tuttavia, se ti trovi di fronte a questo problema, hai diverse opzioni:
-
Sostituisci
useLayoutEffectconuseEffect. Questo dice a React che va bene visualizzare il risultato della renderizzazione iniziale senza impedire al browser di dipingere (perché l’HTML originale diventerà visibile prima che il tuo Effetto venga eseguito). -
In alternativa, chiama
use(browser())per contrassegnare il componente come solo-browser. React sostituirà il suo contenuto fino al<Suspense>più vicino con un fallback di caricamento (ad esempio, uno spinner o un glimmer) durante la renderizzazione lato server. -
In alternativa, contrassegna il tuo componente come solo-client. Questo dice a React di sostituire il suo contenuto fino al
<Suspense>più vicino con un fallback di caricamento durante la renderizzazione lato server. -
In alternativa, puoi renderizzare un componente con
useLayoutEffectsolo dopo l’hydration. Mantieni uno state booleanoisMountedinizializzato afalse, e impostalo atrueall’interno di una chiamata auseEffect. La tua logica di renderizzazione può quindi essere del tiporeturn isMounted ? <RealContent /> : <FallbackContent />. Sul server e durante l’hydration, l’utente vedràFallbackContentche non dovrebbe chiamareuseLayoutEffect. Poi React lo sostituirà conRealContentche viene eseguito solo sul client e può includere chiamate auseLayoutEffect. -
Se sincronizzi il tuo componente con uno store di dati esterno e ti affidi a
useLayoutEffectper motivi diversi dalla misurazione del layout, considerauseSyncExternalStoreche supporta la renderizzazione lato server.