useTransition ti permette di segnare un aggiornamento di stato come non urgente. React renderizza quell'aggiornamento in background e lo scarta o lo riavvia se succede qualcosa di più urgente, come un clic o la pressione di un tasto, così la pagina resta reattiva mentre una parte lenta dello schermo si mette in pari.
Clicca posts e poi, prima che compaia la lista, clicca contact. Contact compare subito: React ha buttato via il rendering di posts a metà. Ora cambia startTransition(() => { setTab(next); }) in un semplice setTab(next); e prova la stessa cosa: la pagina si blocca per circa 300 ms dopo aver cliccato posts, e il tuo clic su contact aspetta che i post siano pronti.
La sintassi
const [isPending, startTransition] = useTransition();
startTransition(() => {
setTab(next);
});
startTransitionriceve una funzione e la esegue subito. Tutti gli aggiornamenti di stato al suo interno vengono segnati come transizioni.isPendingvaletruedal momento in cui avvii la transizione finché React non ha finito di renderizzarla e ha applicato il risultato.
useTransition non accetta argomenti. La funzione che passi viene eseguita in modo sincrono; la transizione riguarda il modo in cui React renderizza l'aggiornamento risultante, non il momento in cui viene eseguita la tua funzione.
Cosa significa "non urgente"
React divide gli aggiornamenti in due tipi. Gli aggiornamenti urgenti, come scrivere, cliccare e premere, devono riflettersi immediatamente, altrimenti la pagina sembra rotta. Gli aggiornamenti di transizione, come passare a una nuova vista o mostrare risultati filtrati, possono richiedere un momento.
React renderizza una transizione a piccoli pezzi e controlla tra un componente e l'altro se è arrivato qualcosa di urgente. Se sì, React mette in pausa o abbandona la transizione, gestisce l'aggiornamento urgente e poi riavvia la transizione con lo stato più recente. Mentre una transizione si renderizza, la schermata precedente resta visibile; React non mostra un risultato costruito a metà. È per questo che l'esempio continua a mostrare il testo di about, con l'indicazione Loading accanto ai pulsanti, finché i post non sono pronti.
L'interruzione avviene tra un componente e l'altro. Un singolo componente che blocca per 300 ms non può essere tagliato a metà, ed è per questo che il lavoro lento qui sopra è distribuito su 300 piccoli componenti SlowPost.
Mostrare un'indicazione di attesa
isPending è il posto giusto per un indicatore di caricamento che non sostituisce il contenuto attuale: uno spinner accanto al pulsante, testo attenuato, un'etichetta "Saving...". Nel primo esempio mostra Loading... accanto alle schede. Puoi usarlo anche per dare stile al contenuto:
<div style={{ opacity: isPending ? 0.6 : 1 }}>{content}</div>
Filtrare una lista grande mentre scrivi
Non puoi mettere il valore stesso di un input in una transizione. Un <input> controllato deve ricevere il suo nuovo valore nello stesso aggiornamento urgente, altrimenti si perdono caratteri. Tieni invece due pezzi di stato: il testo dell'input (urgente) e la query che usa la lista (una transizione).
Scrivi 123 velocemente. L'input non resta mai indietro. La Console registra comunque una riga rendering results per ogni tasto, perché Results registra appena React inizia a renderizzarlo, ma le liste per 1 e 12 non arrivano mai sullo schermo: ogni tasto più recente ha interrotto il rendering in corso, e React è ripartito con la query più recente. Results è racchiuso in memo di proposito: senza, il rendering urgente per ogni tasto renderizzerebbe anche la lista lenta con la vecchia query, e perderesti il vantaggio.
startTransition senza l'hook
react esporta anche startTransition come semplice funzione. Segna gli aggiornamenti come transizioni proprio come fa l'hook, ma non ricevi isPending. Usala dove non puoi chiamare un hook, per esempio in una libreria di dati o in un router al di fuori di qualsiasi componente.
import { startTransition } from 'react';
function navigate(url) {
startTransition(() => {
setCurrentUrl(url);
});
}
Transizioni asincrone in React 19
Da React 19 la funzione che passi può essere async. isPending resta allora true per tutto il tempo in cui la funzione è in esecuzione, attesa compresa, il che la rende un modo semplice per mostrare uno stato di attesa durante una richiesta. La finta saveName qui sotto fa le veci di una richiesta reale.
Nota il secondo startTransition attorno a setSaved. React perde traccia della transizione attraverso un await, quindi un aggiornamento di stato successivo deve essere racchiuso di nuovo per contare come parte della transizione. React chiama action una funzione passata in questo modo; useActionState e le form action si basano sullo stesso meccanismo.
Una transizione non è un debounce
Un debounce aspetta un tempo fisso dopo l'ultimo tasto prima di fare qualsiasi cosa. Una transizione non aspetta. Inizia subito il rendering, e se arriva un aggiornamento più recente scarta il vecchio lavoro. Su un dispositivo veloce il risultato compare senza alcun ritardo; su uno lento, React fa tutto ciò che può tra un tasto e l'altro. Ciò che una transizione non può fare è ridurre il numero di richieste di rete: se ogni tasto avvia un fetch, applica il debounce al fetch.
Transizioni e Suspense
Le transizioni cambiano anche il comportamento di un confine Suspense. Normalmente, quando un componente dentro un confine si sospende (un componente lazy in caricamento, o dati letti con use), React sostituisce il contenuto con il fallback. Se l'aggiornamento che ha causato la sospensione era una transizione, React mantiene invece il contenuto già sullo schermo, e isPending resta true finché il nuovo contenuto non è pronto. È così che i router evitano di mostrare uno spinner grande quanto la pagina a ogni navigazione: racchiudono la navigazione in una transizione, e la vecchia pagina resta visibile finché la successiva non può essere renderizzata. Il contenuto che non è mai stato mostrato usa comunque il fallback, quindi un confine appena rivelato si comporta come al solito.
Cose che non funzionano
- Aggiornamenti dentro un timeout.
startTransition(() => setTimeout(() => setTab(next), 100))non segna l'aggiornamento, perché viene eseguito dopo chestartTransitionè già terminata. Metti invecestartTransitiondentro il timeout. - Un singolo componente lento. React può interrompere solo tra un componente e l'altro, quindi un componente che blocca a lungo continua a bloccare.
- Controllare un input. Come visto sopra, lo stato dell'input stesso resta urgente.
Per il caso in cui non possiedi il setter di stato e ricevi solo un valore, come una prop, useDeferredValue ti dà lo stesso comportamento.
Domande frequenti
Cosa fa useTransition?
Ti dà startTransition, che segna come non urgenti gli aggiornamenti di stato al suo interno, e isPending, che vale true finché quell'aggiornamento è ancora in fase di rendering. React può interrompere un rendering non urgente per gestire prima clic e digitazione.
Qual è la differenza tra useTransition e startTransition?
Entrambi segnano gli aggiornamenti come transizioni. L'hook useTransition ti dà anche isPending per un'indicazione di caricamento. La funzione startTransition importata da react funziona fuori dai componenti ma non ha un indicatore di attesa.
Posso usare useTransition per un input di testo?
Non per il valore dell'input stesso. Un input controllato deve aggiornarsi in modo sincrono, altrimenti la digitazione si rompe. Mantieni urgente lo stato dell'input e metti in una transizione l'aggiornamento costoso che dipende da esso, oppure usa useDeferredValue.
useTransition è la stessa cosa del debouncing?
No. Un debounce aspetta un tempo fisso prima di fare qualsiasi cosa. Una transizione inizia subito il rendering e viene interrotta se arriva un aggiornamento più recente, quindi su un dispositivo veloce il risultato compare senza ritardo.
startTransition può ricevere una funzione async?
Sì, da React 19. isPending resta true finché la funzione async non termina. Gli aggiornamenti di stato dopo un await devono essere racchiusi in un'altra chiamata a startTransition per essere segnati come transizione.