Gestapelte Updates reagieren

So kombinieren Sie Statusaktualisierungen in einem einzigen Renderaufruf

React.js-Batch-Updates

Sie wissen bereits, dass der Diffing-Algorithmus von React jedes Mal, wenn Sie den Zustand einer Komponente aktualisieren, Änderungen im virtuellen DOM erkennt und Ihre Komponente im realen DOM rendert. Aber passiert das wirklich bei jedem einzelnen State-Update? Oder bietet React eine Möglichkeit, diese Updates zu stapeln, sodass mehrere Zustandsmutationen in einem einzigen Rendering-Aufruf kombiniert werden?

Synthetische Ereignisse ermöglichen Batch-Updates

React 17 und niedriger bietet einen Mechanismus, um diese Aktualisierungen zu stapeln. Sie müssen beim Codieren wirklich nicht viel darüber nachdenken, aber zu wissen, wie React Ihren Code optimieren kann, ist dennoch eine großartige Erfahrung.

Hier ist, was passiert.

  • mehrere Zustandsmutationen werden innerhalb eines Callbacks aufgerufen
  • Wenn der Rückruf an ein synthetisches Ereignis angehängt ist, wird React diese Mutationen stapeln
  • Wenn Mutationen gestapelt werden, wird nur ein einziger Render-Aufruf durchgeführt
  • ansonsten führt jede Mutation zu einem neuen Rendering

Um Batching zu ermöglichen, müssen Ihre Zustandsmutationen in einem synthetischen React-Ereignis aufgerufen werden. Zur Erinnerung, synthetische React-Ereignisse sind im Grunde nur ein Wrapper um native HTML-Ereignisse, um die gleiche Funktionalität in allen Browsern bereitzustellen. Jeder synthetische Event-Handler kann direkt einer Komponente als Prop bereitgestellt werden.

Um alles in Aktion zu sehen, ist hier ein Beispielcode, der es React ermöglichen würde, alle Zustandsmutationen in einem einzigen Rendering zu kombinieren, anstatt ein Rendering pro 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>
  );
}

Gestapelte Updates für React 18 und höher

Mit der nächsten Version von React (zumindest während ich dies schreibe) wird der Batching-Mechanismus deutlich verbessert. Anstatt nur in synthetischen Ereignissen möglich zu sein, können auch andere Arten von Rückrufen verwendet werden.

  • Auszeiten
  • Versprechen
  • native Ereignishandler

Diese Änderung führt höchstwahrscheinlich zu einer besseren Leistung Ihrer App, da weniger Rendering automatisch durchgeführt wird.

Vermeidung von Batch-Updates in React 18 und höher

Ab React 18 haben Sie die Möglichkeit, bei jeder Zustandsmutation explizit ein neues Rendern auszulösen, wodurch ein Batch-Update vermieden wird. Dies wird höchstwahrscheinlich nur in Grenzfällen verwendet, aber es ist trotzdem interessant zu wissen.

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