useDeferredValue

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.

useDeferredValue è un Hook React che ti permette di differire l’aggiornamento di una parte dell’UI.

const deferredValue = useDeferredValue(value)

Reference

useDeferredValue(value, initialValue?)

Chiama useDeferredValue al top level del tuo componente per ottenere una versione differita di quel valore.

import { useState, useDeferredValue } from 'react';

function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}

Vedi altri esempi sotto.

Parameters

  • value: Il valore che vuoi differire. Può essere di qualsiasi tipo.
  • optional initialValue: Un valore da usare durante la renderizzazione iniziale del componente. Se ometti questa opzione, useDeferredValue non differirà durante la renderizzazione iniziale, perché non esiste una versione precedente di value da renderizzare al suo posto.

Returns

  • currentValue: Durante la renderizzazione iniziale, il valore differito restituito sarà initialValue, oppure lo stesso del valore che hai fornito. Durante gli aggiornamenti, React proverà prima a ri-renderizzare con il vecchio valore (quindi restituirà il vecchio valore), e poi tenterà un’altra ri-renderizzazione in background con il nuovo valore (quindi restituirà il valore aggiornato).

Caveats

  • Quando un aggiornamento avviene all’interno di una Transizione, useDeferredValue restituisce sempre il nuovo value e non avvia una renderizzazione differita, perché l’aggiornamento è già differito.

  • I valori che passi a useDeferredValue dovrebbero essere valori primitivi (come stringhe e numeri) oppure oggetti creati al di fuori della renderizzazione. Se crei un nuovo oggetto durante la renderizzazione e lo passi subito a useDeferredValue, sarà diverso a ogni renderizzazione, causando ri-renderizzazioni in background non necessarie.

  • Quando useDeferredValue riceve un valore diverso (rispetto a Object.is), oltre alla renderizzazione corrente (quando usa ancora il valore precedente), pianifica una ri-renderizzazione in background con il nuovo valore. La ri-renderizzazione in background è interrompibile: se c’è un altro aggiornamento a value, React riavvierà la ri-renderizzazione in background da zero. Per esempio, se l’utente digita in un input più velocemente di quanto un grafico che riceve il suo valore differito riesca a ri-renderizzarsi, il grafico si ri-renderizzerà solo dopo che l’utente smette di digitare.

  • useDeferredValue è integrato con <Suspense>. Se l’aggiornamento in background causato da un nuovo valore sospende l’UI, l’utente non vedrà il fallback. Vedrà il vecchio valore differito finché i dati non saranno caricati.

  • useDeferredValue da solo non impedisce richieste di rete extra.

  • Non c’è un ritardo fisso causato da useDeferredValue in sé. Non appena React termina la ri-renderizzazione originale, inizierà subito a lavorare sulla ri-renderizzazione in background con il nuovo valore differito. Qualsiasi aggiornamento causato da eventi (come la digitazione) interromperà la ri-renderizzazione in background e avrà priorità su di essa.

  • La ri-renderizzazione in background causata da useDeferredValue non esegue gli Effetti finché non viene committata sullo schermo. Se la ri-renderizzazione in background sospende, i suoi Effetti verranno eseguiti dopo che i dati saranno caricati e l’UI sarà aggiornata.


Usage

Mostrare contenuto obsoleto mentre il contenuto aggiornato è in caricamento

Chiama useDeferredValue al top level del tuo componente per differire l’aggiornamento di una parte dell’UI.

import { useState, useDeferredValue } from 'react';

function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}

Durante la renderizzazione iniziale, il valore differito sarà lo stesso del valore che hai fornito.

Durante gli aggiornamenti, il valore differito resterà “indietro” rispetto all’ultimo valore. In particolare, React ri-renderizzerà prima senza aggiornare il valore differito, e poi proverà a ri-renderizzare con il valore appena ricevuto in background.

Vediamo un esempio per capire quando questo è utile.

Nota bene

Questo esempio presuppone che tu usi una sorgente dati che attiva un boundary Suspense, come una Promise che leggi con use.

Scopri di più su Suspense.

In questo esempio, il componente SearchResults sospende mentre recupera i risultati della ricerca. Prova a digitare "a", attendi i risultati, e poi modifica in "ab". I risultati per "a" vengono sostituiti dal fallback di caricamento.

import { Suspense, useState } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <SearchResults query={query} />
      </Suspense>
    </>
  );
}

Un pattern UI alternativo comune consiste nel differire l’aggiornamento dell’elenco dei risultati e continuare a mostrare i risultati precedenti finché quelli nuovi non sono pronti. Chiama useDeferredValue per passare in giù una versione differita della query:

export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Search albums:
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Loading...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}

query si aggiornerà subito, quindi l’input mostrerà il nuovo valore. Tuttavia, deferredQuery manterrà il valore precedente finché i dati non saranno caricati, quindi SearchResults mostrerà per un po’ i risultati obsoleti.

Digita "a" nell’esempio sotto, attendi che i risultati si carichino, e poi modifica l’input in "ab". Nota come, invece del fallback Suspense, ora vedi l’elenco obsoleto dei risultati finché quelli nuovi non sono caricati:

import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <SearchResults query={deferredQuery} />
      </Suspense>
    </>
  );
}

Approfondimento

Come funziona il differimento di un valore sotto il cofano?

Puoi pensarlo come un processo in due passaggi:

  1. Per prima cosa, React ri-renderizza con la nuova query ("ab") ma con la vecchia deferredQuery (ancora "a"). Il valore deferredQuery, che passi all’elenco dei risultati, è differito: resta “indietro” rispetto al valore query.

  2. In background, React prova a ri-renderizzare con entrambi query e deferredQuery aggiornati a "ab". Se questa ri-renderizzazione si completa, React la mostrerà sullo schermo. Tuttavia, se sospende (i risultati per "ab" non sono ancora caricati), React abbandonerà questo tentativo di renderizzazione e riproverà questa ri-renderizzazione dopo che i dati saranno caricati. L’utente continuerà a vedere il valore differito obsoleto finché i dati non saranno pronti.

La renderizzazione “in background” differita è interrompibile. Per esempio, se digiti di nuovo nell’input, React la abbandonerà e ripartirà con il nuovo valore. React userà sempre l’ultimo valore fornito.

Nota che c’è comunque una richiesta di rete per ogni pressione di tasto. Ciò che viene differito qui è la visualizzazione dei risultati (finché non sono pronti), non le richieste di rete in sé. Anche se l’utente continua a digitare, le risposte per ogni pressione di tasto vengono messe in cache, quindi premere Backspace è istantaneo e non effettua di nuovo il fetch.


Indicare che il contenuto è obsoleto

Nell’esempio sopra, non c’è alcuna indicazione che l’elenco dei risultati per l’ultima query sia ancora in caricamento. Questo può confondere l’utente se i nuovi risultati impiegano un po’ a caricarsi. Per evidenziare che l’elenco dei risultati non corrisponde all’ultima query, puoi aggiungere un’indicazione visiva quando viene mostrato l’elenco obsoleto:

<div style={{
opacity: query !== deferredQuery ? 0.5 : 1,
}}>
<SearchResults query={deferredQuery} />
</div>

Con questa modifica, non appena inizi a digitare, l’elenco obsoleto dei risultati viene leggermente attenuato finché il nuovo elenco non si carica. Puoi anche aggiungere una transizione CSS per ritardare l’attenuazione in modo che risulti graduale, come nell’esempio sotto:

import { Suspense, useState, useDeferredValue } from 'react';
import SearchResults from './SearchResults.js';

export default function App() {
  const [query, setQuery] = useState('');
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
  return (
    <>
      <label>
        Search albums:
        <input value={query} onChange={e => setQuery(e.target.value)} />
      </label>
      <Suspense fallback={<h2>Loading...</h2>}>
        <div style={{
          opacity: isStale ? 0.5 : 1,
          transition: isStale ? 'opacity 0.2s 0.2s linear' : 'opacity 0s 0s linear'
        }}>
          <SearchResults query={deferredQuery} />
        </div>
      </Suspense>
    </>
  );
}


Differire la ri-renderizzazione di una parte dell’UI

Puoi anche usare useDeferredValue come ottimizzazione delle prestazioni. È utile quando una parte dell’UI è lenta da ri-renderizzare, non c’è un modo semplice per ottimizzarla, e vuoi evitare che blocchi il resto dell’UI.

Immagina di avere un campo di testo e un componente (come un grafico o un elenco lungo) che si ri-renderizza a ogni pressione di tasto:

function App() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={text} />
</>
);
}

Per prima cosa, ottimizza SlowList per saltare la ri-renderizzazione quando le sue props sono le stesse. Per farlo, avvolgilo in memo:

const SlowList = memo(function SlowList({ text }) {
// ...
});

Tuttavia, questo aiuta solo se le props di SlowList sono le stesse rispetto alla renderizzazione precedente. Il problema che affronti ora è che è lento quando sono diverse, e quando devi effettivamente mostrare un output visivo diverso.

In concreto, il problema principale di prestazioni è che ogni volta che digiti nell’input, SlowList riceve nuove props, e ri-renderizzare l’intero albero rende la digitazione a scatti. In questo caso, useDeferredValue ti permette di dare priorità all’aggiornamento dell’input (che deve essere veloce) rispetto all’aggiornamento dell’elenco dei risultati (che può essere più lento):

function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={deferredText} />
</>
);
}

Questo non rende più veloce la ri-renderizzazione di SlowList. Tuttavia, dice a React che la ri-renderizzazione dell’elenco può essere deprioritizzata in modo da non bloccare le pressioni di tasto. L’elenco resterà “indietro” rispetto all’input e poi “recupererà”. Come prima, React proverà ad aggiornare l’elenco il prima possibile, ma non bloccherà l’utente dalla digitazione.

La differenza tra useDeferredValue e la ri-renderizzazione non ottimizzata

Esempio 1 di 2:
Ri-renderizzazione differita dell’elenco

In questo esempio, ogni elemento del componente SlowList è artificialmente rallentato così puoi vedere come useDeferredValue ti permette di mantenere l’input reattivo. Digita nell’input e nota come la digitazione resta fluida mentre l’elenco resta “indietro”.

import { useState, useDeferredValue } from 'react';
import SlowList from './SlowList.js';

export default function App() {
  const [text, setText] = useState('');
  const deferredText = useDeferredValue(text);
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <SlowList text={deferredText} />
    </>
  );
}

Insidia

Questa ottimizzazione richiede che SlowList sia avvolto in memo. Questo perché ogni volta che text cambia, React deve poter ri-renderizzare rapidamente il componente genitore. Durante quella ri-renderizzazione, deferredText ha ancora il valore precedente, quindi SlowList può saltare la ri-renderizzazione (le sue props non sono cambiate). Senza memo, dovrebbe ri-renderizzarsi comunque, vanificando lo scopo dell’ottimizzazione.

Approfondimento

In che modo differire un valore è diverso da debouncing e throttling?

Ci sono due tecniche di ottimizzazione comuni che potresti aver usato prima in questo scenario:

  • Debouncing significa che aspetteresti che l’utente smetta di digitare (per esempio per un secondo) prima di aggiornare l’elenco.
  • Throttling significa che aggiorneresti l’elenco ogni tanto (per esempio al massimo una volta al secondo).

Se queste tecniche sono utili in alcuni casi, useDeferredValue è più adatto a ottimizzare la renderizzazione perché è profondamente integrato con React e si adatta al dispositivo dell’utente.

A differenza di debouncing o throttling, non richiede di scegliere un ritardo fisso. Se il dispositivo dell’utente è veloce (per esempio un laptop potente), la ri-renderizzazione differita avverrebbe quasi subito e non sarebbe percettibile. Se il dispositivo dell’utente è lento, l’elenco resterà “indietro” rispetto all’input in proporzione a quanto è lento il dispositivo.

Inoltre, a differenza di debouncing o throttling, le ri-renderizzazioni differite fatte da useDeferredValue sono interrompibili per impostazione predefinita. Questo significa che se React è nel mezzo della ri-renderizzazione di un elenco grande, ma l’utente preme un altro tasto, React abbandonerà quella ri-renderizzazione, gestirà la pressione di tasto, e poi ricomincerà a renderizzare in background. Al contrario, debouncing e throttling producono comunque un’esperienza a scatti perché sono bloccanti: posticipano solo il momento in cui la renderizzazione blocca la pressione di tasto.

Se il lavoro che stai ottimizzando non avviene durante la renderizzazione, debouncing e throttling restano utili. Per esempio, possono farti effettuare meno richieste di rete. Puoi anche usare queste tecniche insieme.