useDeferredValue
useDeferredValue
est un Hook React qui vous laisse différer la mise à jour d’une partie de l’interface utilisateur (UI, NdT).
const deferredValue = useDeferredValue(value)
Référence
useDeferredValue(value, initialValue?)
Appelez useDeferredValue
à la racine de votre composant pour recevoir une version différée de cette valeur.
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
Voir d’autres exemples ci-dessous.
Paramètres
value
: la valeur que vous souhaitez différer. Elle peut être de n’importe quel type.- Canary uniquement
initialValue
optionnelle : une valeur à utiliser lors du rendu initial d’un composant. Si cette option est manquante,useDeferredValue
ne différera pas lors du rendu initial, faute d’une version précédente devalue
à lui substituer lors du rendu.
Valeur renvoyée
currentValue
: durant le rendu initial, la valeur différée renvoyée sera celle que vous avez fournie. Lors des mises à jour, React tentera d’abord un rendu avec l’ancienne valeur (il va donc renvoyer l’ancienne valeur), et ensuite essayer en arrière-plan un rendu avec la nouvelle valeur (il va donc renvoyer la valeur à jour).
Limitations
-
Lors d’une mise à jour au sein d’une Transition,
useDeferredValue
renverra toujours la nouvellevalue
et ne déclenchera pas un rendu différé, puisque la mise à jour est déjà différée. -
Les valeurs que vous passez à
useDeferredValue
doivent être soit des valeurs primitives (comme des chaînes de caractères ou des nombres), soit des objets créés en-dehors du rendu. Si vous créez un nouvel objet pendant le rendu et que vous le passez immédiatement àuseDeferredValue
, il sera différent à chaque rendu, entraînant des rendus inutiles en arrière-plan. -
Quand
useDeferredValue
reçoit une valeur différente (en comparant au moyen deObject.is
), en plus du rendu en cours (dans lequel il utilisera encore la valeur précédente), il planifie un rendu supplémentaire en arrière-plan avec la nouvelle valeur. Ce rendu d’arrière-plan est susceptible d’être interrompu : s’il y a un nouvelle mise à jour devalue
, React le recommencera de zéro. Par exemple, si l’utilisateur tape dans un champ de saisie trop rapidement pour qu’un graphique basé sur sa valeur différée puisse suivre, le graphique ne se mettra à jour qu’une fois que l’utilisateur aura terminé sa saisie. -
useDeferredValue
s’intègre très bien avec<Suspense>
. Si la mise à jour d’arrière-plan suspend l’UI, l’utilisateur ne verra pas l’UI de secours : il continuera à voir l’ancienne valeur différée jusqu’à ce que les données soient chargées. -
useDeferredValue
n’empêche pas par lui-même des requêtes réseau supplémentaires. -
useDeferredValue
ne recourt pas à un différé de durée fixe. Dès que React termine le premier nouveau rendu, il commence immédiatement à travailler sur le rendu d’arrière-plan avec la nouvelle valeur différée. Toute mise à jour causée par des évènements (comme écrire dans un champ de saisie) interrompra le rendu d’arrière-plan et sera traitée en priorité. -
Le rendu d’arrière-plan entraîné par un
useDeferredValue
ne déclenche pas les Effets tant qu’il n’est pas retranscrit à l’écran. Si le rendu d’arrière-plan suspend, ses Effets ne seront lancés qu’après que les données seront chargées et que l’UI sera mise à jour.
Utilisation
Afficher du contenu obsolète pendant le chargement du nouveau contenu
Appelez useDeferredValue
à la racine de votre composant pour différer la mise à jour de certaines parties de votre interface utilisateur.
import { useState, useDeferredValue } from 'react';
function SearchPage() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
// ...
}
Lors du rendu initial, la valeur différée sera la même que la valeur que vous avez fournie.
Lors des mises à jour, la valeur différée sera « en retard » par rapport à la dernière valeur. Plus particulièrement, React fera d’abord un rendu sans mettre à jour la valeur différée, puis tentera un rendu supplémentaire en arrière-plan avec la nouvelle valeur reçue.
Parcourons un exemple afin de comprendre l’utilité de ce Hook.
Dans cet exemple, le composant SearchResults
suspend pendant le chargement des résultats de recherche. Essayez de saisir "a"
, attendez que les résultats s’affichent, puis modifiez la saisie en "ab"
. Les résultats pour "a"
sont remplacés par une UI de secours pendant le chargement.
import { Suspense, useState } from 'react'; import SearchResults from './SearchResults.js'; export default function App() { const [query, setQuery] = useState(''); return ( <> <label> Rechercher des albums : <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Chargement...</h2>}> <SearchResults query={query} /> </Suspense> </> ); }
Une alternative visuelle courante consiste à différer la mise à jour d’une liste de résultats, en continuant à montrer les anciens résultats jusqu’à ce que les nouveaux soient prêts. Appelez useDeferredValue
pour pouvoir passer une version différée de la recherche :
export default function App() {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
return (
<>
<label>
Rechercher des albums :
<input value={query} onChange={e => setQuery(e.target.value)} />
</label>
<Suspense fallback={<h2>Chargement...</h2>}>
<SearchResults query={deferredQuery} />
</Suspense>
</>
);
}
La query
va se mettre à jour immédiatement, donc le champ de saisie affichera la nouvelle valeur. En revanche, la deferredQuery
gardera son ancienne valeur jusqu’à ce que les données soient chargées, et SearchResults
affichera les anciens résultats dans l’intervalle.
Tapez"a"
dans l’exemple ci-dessous, attendez que les résultats soient chargés, et modifiez ensuite votre saisie pour "ab"
. Remarquez qu’au lieu d’apercevoir l’interface de chargement, vous continuez à voir la liste des anciens résultats jusqu’à ce que les nouveaux résultats soient chargés :
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> Rechercher des albums : <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Chargement...</h2>}> <SearchResults query={deferredQuery} /> </Suspense> </> ); }
En détail
Imaginez un déroulement en deux étapes :
-
Pour commencer, React refait un rendu avec la nouvelle
query
("ab"
) mais avec l’anciennedeferredQuery
(toujours"a")
. La valeurdeferredQuery
, que vous passez à la liste de résultats, est différée : elle est « en retard » par rapport à la valeurquery
. -
En arrière-plan, React tente alors un autre rendu avec
query
etdeferredQuery
valant toutes les deux"ab"
. Si ce rendu aboutit, React l’affichera à l’écran. Cependant, s’il suspend (les résultats pour"ab"
ne sont pas encore chargés), React abandonnera cet essai de rendu, et essaiera à nouveau une fois les données chargées. L’utilisateur continuera à voir l’ancienne valeur différée jusqu’à ce que les données soient prêtes.
Le rendu différé « d’arrière-plan » est susceptible d’être interrompu. Par exemple, si vous tapez à nouveau dans le champ de saisie, React l’abandonnera et recommencera avec la nouvelle valeur. React utilisera toujours la dernière valeur fournie.
Remarquez qu’il y a quand même une requête réseau par frappe clavier. Ce qui est différé ici, c’est l’affichage des résultats (jusqu’à ce qu’ils soient prêts), et non pas les requêtes réseau elles-mêmes. Même si l’utilisateur continue à saisir, les réponses pour chaque frappe clavier sont mises en cache, donc les données ne sont pas chargées à nouveau lorsqu’on appuie sur Backspace : la mise à jour est alors instantanée.
Indiquer que le contenu est obsolète
Dans l’exemple ci-avant, il n’y aucune indication que la liste des résultats pour la dernière requête est toujours en train de charger. Cela peut être déroutant pour l’utilisateur si les nouveaux résultats prennent du temps à charger. Afin de bien signifier que la liste de résultats ne reflète pas encore la dernière recherche, vous pouvez ajouter une indication visuelle lorsque l’ancienne liste de résultats est affichée :
<div style={{
opacity: query !== deferredQuery ? 0.5 : 1,
}}>
<SearchResults query={deferredQuery} />
</div>
Avec ce changement, dès que vous commencerez à taper, l’ancienne liste de résultats sera légèrement assombrie, jusqu’à ce que la nouvelle liste de résultats soit chargée. Vous pouvez également ajouter une transition CSS pour un résultat plus graduel, comme dans l’exemple ci-dessous :
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> Rechercher des albums: <input value={query} onChange={e => setQuery(e.target.value)} /> </label> <Suspense fallback={<h2>Chargement...</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> </> ); }
Différer le rendu d’une partie de l’UI
Vous pouvez également utiliser useDeferredValue
pour optimiser les performances. C’est pratique lorsqu’une partie de votre UI a un rendu lent, qu’il n’y a pas de manière simple de l’optimiser, et que vous voulez éviter qu’elle bloque le reste de l’UI.
Imaginez que vous avez un champ textuel et un composant (comme un graphique ou une longue liste) qui refait son rendu à chaque frappe clavier :
function App() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={text} />
</>
);
}
Pour commencer, optimisez SlowList
pour éviter un nouveau rendu quand ses propriétés n’ont pas changé. Pour ce faire, enrobez-le avec memo
:
const SlowList = memo(function SlowList({ text }) {
// ...
});
Cependant, ça ne vous aide que si les propriétés de SlowList
sont les mêmes que lors du rendu précédent. Ce composant peut toujours être lent lorsque les propriétés sont différentes, et que vous avez effectivement besoin de produire un rendu visuel distinct.
Concrètement, le souci de performances principal vient de ce que lorsque vous tapez dans le champ de saisie, la SlowList
reçoit des nouvelles propriétés, et la lenteur de sa mise à jour rend la saisie saccadée. Dans un tel cas, useDeferredValue
vous permet de prioriser la mise à jour du champ de saisie (qui doit être rapide) par rapport à celle de la liste de résultats (qui peut être plus lente) :
function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<SlowList text={deferredText} />
</>
);
}
Ça n’accélère pas le rendu de SlowList
. Néanmoins, ça indique à React de déprioriser le rendu de la liste afin de ne pas bloquer les frappes clavier. La liste sera « en retard » par rapport au champ de saisie, pour finalement le « rattraper ». Comme auparavant, React essaiera de mettre à jour la liste le plus vite possible, mais sans empêcher l’utilisateur de taper.
Exemple 1 sur 2 · Différer le rendu de la liste
Dans cet exemple, chaque élément du composant SlowList
est artificiellement ralenti afin que vous puissiez constater que useDeferredValue
permet de garder le champ de saisie réactif. Écrivez dans le champ de saisie et voyez comme la saisie reste réactive, alors que la liste « est en retard ».
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} /> </> ); }
En détail
Il existe deux technique d’optimisation courantes que vous avez peut-être utilisées auparavant dans ce genre de scénarios :
- Le debouncing signifie que vous attendriez que l’utilisateur cesse de taper (par exemple pendant une seconde) avant de mettre à jour la liste.
- Le throttling signifie que vous ne mettriez à jour la liste qu’à une fréquence limitée (par exemple au maximum une fois par seconde).
Mêmes si ces techniques sont utiles dans certains cas, useDeferredValue
est plus adapté pour optimiser le rendu, car il est totalement intégré avec React et il s’adapte à l’appareil de l’utilisateur.
Contrairement au debouncing et au throttling, il ne nécessite pas de choisir un délai fixe. Si l’appareil de l’utilisateur est rapide (par exemple un ordinateur puissant), le rendu différé serait quasiment immédiat, le rendant imperceptible pour l’utilisateur. Si l’appareil est lent, la liste serait « en retard » par rapport au champ de saisie, proportionnellement à la lenteur de l’appareil.
De plus, les rendus différés planifiés par useDeferredValue
sont par défaut susceptibles d’être interrompus, ce qui n’est pas le cas du debouncing ou du throttling. Ça signifie que si React est en plein milieu du rendu d’une vaste liste, et que l’utilisateur ajuste sa saisie, React abandonnera ce rendu, traitera la frappe, et recommencera le rendu en arrière-plan. Par opposition, le debouncing et le throttling donneraient ici toujours une expérience saccadée car ils sont bloquants : ils diffèrent simplement le moment auquel le rendu bloque la frappe.
Si vous souhaitez optimiser des traitements hors du rendu, le debouncing et le throttling restent utiles. Par exemple, ils peuvent vous permettre de lancer moins de de requêtes réseau. Vous pouvez parfaitement combiner ces techniques.