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.