useLayoutEffect

Nota bene

Questa pagina è stata tradotta automaticamente e supervisionata da un maintainer. Un’ulteriore revisione da parte della community sarebbe comunque utile. Migliora questa traduzione.

Insidia

useLayoutEffect può penalizzare le prestazioni. Preferisci useEffect quando possibile.

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);
}, []);
// ...

Vedi altri esempi sotto.

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 di setup. 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 confronto Object.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 useLayoutEffect e tutti gli aggiornamenti di state programmati da esso impediscono al browser di ridipingere lo schermo. Se usato eccessivamente, rende l’app lenta. Quando possibile, preferisci useEffect.

  • Se attivi un aggiornamento di state all’interno di useLayoutEffect, React eseguirà immediatamente tutti gli Effetti rimanenti, incluso useEffect.


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:

  1. Renderizza il tooltip ovunque (anche con una posizione sbagliata).
  2. Misura la sua altezza e decidi dove posizionare il tooltip.
  3. 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:

  1. Tooltip renderizza con tooltipHeight = 0 iniziale (quindi il tooltip potrebbe essere posizionato in modo errato).
  2. React lo posiziona nel DOM ed esegue il codice in useLayoutEffect.
  3. Il tuo useLayoutEffect misura l’altezza del contenuto del tooltip e attiva una ri-renderizzazione immediata.
  4. Tooltip renderizza di nuovo con la tooltipHeight reale (quindi il tooltip è posizionato correttamente).
  5. 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.

useLayoutEffect vs useEffect

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
  );
}

Nota bene

Renderizzare in due passaggi e impedire al browser di ridipingere penalizza le prestazioni. Cerca di evitarlo quando puoi.


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:

  1. Renderizza il contenuto iniziale.
  2. Misura il layout prima che il browser ridipinge lo schermo.
  3. 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 useLayoutEffect con useEffect. 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 useLayoutEffect solo dopo l’hydration. Mantieni uno state booleano isMounted inizializzato a false, e impostalo a true all’interno di una chiamata a useEffect. La tua logica di renderizzazione può quindi essere del tipo return isMounted ? <RealContent /> : <FallbackContent />. Sul server e durante l’hydration, l’utente vedrà FallbackContent che non dovrebbe chiamare useLayoutEffect. Poi React lo sostituirà con RealContent che viene eseguito solo sul client e può includere chiamate a useLayoutEffect.

  • Se sincronizzi il tuo componente con uno store di dati esterno e ti affidi a useLayoutEffect per motivi diversi dalla misurazione del layout, considera useSyncExternalStore che supporta la renderizzazione lato server.