Reagisci agli aggiornamenti in batch

Come combinare gli aggiornamenti di stato in una singola chiamata di rendering

Aggiornamenti in batch di React.js

Sai già che ogni volta che aggiorni lo stato di un componente, l'algoritmo di diffing di React rileverà le modifiche nel DOM virtuale e renderà il tuo componente nel DOM reale. Ma questo accade davvero per ogni singolo aggiornamento di stato? Oppure React fornisce un modo per raggruppare questi aggiornamenti, in modo che più mutazioni di stato vengano combinate in una singola chiamata di rendering?

Gli eventi sintetici consentono aggiornamenti batch

React 17 e versioni precedenti forniscono un meccanismo per raggruppare tali aggiornamenti. Non devi pensarci molto durante la codifica, ma sapere come React potrebbe ottimizzare il tuo codice è comunque un ottimo apprendimento.

Ecco cosa sta succedendo.

  • più mutazioni di stato sono chiamate all'interno di un callback
  • se il callback è collegato a un evento sintetico, React collegherà quelle mutazioni
  • quando le mutazioni vengono raggruppate, viene eseguita solo una singola chiamata di rendering
  • altrimenti, ogni mutazione porta a una nuova resa

Per consentire il batching, le tue mutazioni di stato devono essere chiamate all'interno di un evento React sintetico. Come rapido promemoria, gli eventi sintetici di React sono fondamentalmente solo un wrapper per gli eventi HTML nativi per fornire la stessa funzionalità in tutti i browser. Ogni gestore di eventi sintetico può essere fornito direttamente a un componente come prop.

Per vedere tutto in azione, ecco un codice di esempio che consentirebbe a React di combinare tutte le mutazioni di stato in un singolo rendering, invece di un rendering per mutazione.

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

Aggiornamenti in batch per React 18 e versioni successive

Con la prossima versione di React (almeno mentre sto scrivendo questo), il meccanismo di batch sarà molto più migliorato. Invece di essere possibili solo negli eventi sintetici, saranno utilizzabili anche altri tipi di callback.

  • timeout
  • promesse
  • gestori di eventi nativi

Questa modifica molto probabilmente porterà a prestazioni migliori della tua app, poiché verrà eseguito automaticamente un minor numero di rendering.

Evitare aggiornamenti in batch in React 18 e versioni successive

A partire da React 18, avrai la possibilità di attivare esplicitamente un nuovo rendering con ogni mutazione di stato, evitando così un aggiornamento in batch. Molto probabilmente verrà utilizzato solo nei casi limite, ma è comunque interessante saperlo.

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