Réagir aux mises à jour groupées

Comment combiner les mises à jour d'état dans un seul appel de rendu

Mises à jour par lots de React.js

Vous savez déjà que chaque fois que vous mettez à jour l'état d'un composant, l'algorithme de différence de React détectera les changements dans le DOM virtuel et rendra votre composant dans le DOM réel. Mais cela se produit-il vraiment pour chaque mise à jour d'état ? Ou React fournit-il un moyen de regrouper ces mises à jour, de sorte que plusieurs mutations d'état soient combinées en un seul appel de rendu ?

Les événements synthétiques permettent des mises à jour par lots

React 17 et versions antérieures fournissent un mécanisme pour regrouper ces mises à jour. Vous n'avez vraiment pas besoin d'y penser beaucoup pendant le codage, mais savoir comment React peut optimiser votre code est néanmoins un excellent apprentissage.

Voici ce qui se passe.

  • plusieurs mutations d'état sont appelées dans un rappel
  • si le rappel est attaché à un événement synthétique, React regroupera ces mutations
  • lorsque les mutations sont groupées, un seul appel de rendu est effectué
  • sinon, chaque mutation conduit à un nouveau rendu

Pour permettre le traitement par lots, vos mutations d'état doivent être appelées dans un événement React synthétique. Pour rappel, les événements synthétiques React ne sont fondamentalement qu'un wrapper autour des événements HTML natifs pour fournir la même fonctionnalité sur tous les navigateurs. Chaque gestionnaire d'événement synthétique peut être fourni directement à un composant en tant que prop.

Pour tout voir en action, voici un exemple de code qui permettrait à React de combiner toutes les mutations d'état en un seul rendu, au lieu d'un rendu par mutation.

function Component() {
  const [id, setId] = useState(0);
  const [date, setDate] = useState(new Date());
  const [random, setRandom] = useState(Math.random());

  // A lame callback, but it's just for
  // the demo. Note that clicking on the
  // button will only lead to a single
  // re-render!
  //
  // This is b/c the callback is provided to
  // 'onClick', a synthetic event.
  const onButtonClick = () => {
    setDate(new Date());
    setId(prevId => prevId + 1);
  }
  
  console.log("Render:", id, date);

  return (
    <div>
      <button 
        onClick={onButtonClick}>
        Update state
      </button>
    </div>
  );
}
//
// Almost the same as the previous
// example, but this time w/o
// a batched upate call.
//

function Component() {
  const [id, setId] = useState(0);
  const [date, setDate] = useState(new Date());
  const [random, setRandom] = useState(Math.random());

  // No batching, as the state
  // mutations happend inside
  // a setTimeout - even though
  // the timeout is inside a 
  // synthetic event.
  const onButtonClick = () => {
    setTimeout(() => {
      setDate(new Date());
      setId(prevId => prevId + 1);
    }, 0);
  }
  
  console.log("Render:", id, date);

  return (
    <div>
      <button 
        onClick={onButtonClick}>
        Update state
      </button>
    </div>
  );
}

Mises à jour groupées pour React 18 et versions ultérieures

Avec la prochaine version de React (du moins pendant que j'écris ceci), le mécanisme de traitement par lots sera beaucoup plus amélioré. Au lieu d'être uniquement possibles dans les événements synthétiques, d'autres types de rappels seront également utilisables.

  • délais d'attente
  • promesses
  • gestionnaires d'événements natifs

Ce changement conduira très probablement à de meilleures performances de votre application, car moins de rendus seront effectués automatiquement.

Éviter les mises à jour par lots dans React 18 et versions ultérieures

À partir de React 18, vous aurez la possibilité de déclencher explicitement un nouveau rendu à chaque mutation d'état, évitant ainsi une mise à jour par lots. Cela ne sera probablement utilisé que dans les cas extrêmes, mais il est néanmoins intéressant de le savoir.

//  
// This example is almost
// 1:1 from Dan Abramov's
// example.
//
// Use 'flushSync' from the
// 'react-dom'-package.
import { flushSync } from 'react-dom'; 

function handleClick() {
  flushSync(() => {
    setId(prevId => prevId + 1);
  });
  // React has updated the DOM by now
  flushSync(() => {
    setDate(new Date());
  });
  // React has updated the DOM by now
}