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 de value Ă  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).

Canary (fonctionnalité expérimentale)

Dans les derniÚres versions React Canary, useDeferredValue renvoie la initialValue lors du rendu initial, puis planifie un nouceau rendu en arriÚre-plan avec la value renvoyée.

Limitations

  • Lors d’une mise Ă  jour au sein d’une Transition, useDeferredValue renverra toujours la nouvelle value 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 de Object.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 de value, 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.

Remarque

Cet exemple part du principe que vous utilisez une source de donnĂ©e compatible avec Suspense :

  • Le chargement de donnĂ©es fourni par des frameworks intĂ©grant Suspense tels que Relay et Next.js
  • Le chargement Ă  la demande de composants avec lazy
  • La lecture de la valeur d’une promesse avec use

Apprenez-en davantage sur Suspense et ses limitations.

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

Comment une valeur diffĂ©rĂ©e fonctionne-t-elle sous le capot ?

Imaginez un dĂ©roulement en deux Ă©tapes :

  1. Pour commencer, React refait un rendu avec la nouvelle query ("ab") mais avec l’ancienne deferredQuery (toujours "a"). La valeur deferredQuery, que vous passez Ă  la liste de rĂ©sultats, est diffĂ©rĂ©e : elle est « en retard Â» par rapport Ă  la valeur query.

  2. En arriĂšre-plan, React tente alors un autre rendu avec query et deferredQuery 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.

Différence entre useDeferredValue et un rendu non optimisé

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

PiĂšge

Cette optimisation nĂ©cessite que SlowList soit enrobĂ©e par un memo. En effet, Ă  chaque fois que text change, React doit pouvoir refaire le rendu du composant parent rapidement. Durant ce rendu, deferredText a toujours sa valeur prĂ©cĂ©dente, donc SlowList peut s’épargner un nouveau rendu (ses propriĂ©tĂ©s n’ont pas changĂ©). Sans memo, il y aurait un nouveau rendu dans tous les cas, ce qui tue tout l’intĂ©rĂȘt de l’optimisation.

En détail

Valeur diffĂ©rĂ©e, debouncing et throttling : quelles diffĂ©rences ?

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.