Reaccionar actualizaciones por lotes

Cómo combinar actualizaciones de estado en una sola llamada de procesamiento

Actualizaciones por lotes de React.js

Ya sabe que cada vez que actualice el estado de un componente, el algoritmo de diferenciación de React detectará cambios en el DOM virtual y renderizará su componente en el DOM real. Pero, ¿esto realmente sucede con cada actualización de estado? ¿O React proporciona una forma de agrupar esas actualizaciones, de modo que múltiples mutaciones de estado se combinen en una sola llamada de procesamiento?

Los eventos sintéticos permiten actualizaciones de lotes

React 17 y versiones inferiores proporcionan un mecanismo para agrupar esas actualizaciones. Realmente no tienes que pensar mucho en ello mientras codificas, pero saber cómo React podría optimizar tu código es, sin embargo, un gran aprendizaje.

Esto es lo que está sucediendo.

  • múltiples mutaciones de estado se llaman dentro de una devolución de llamada
  • si la devolución de llamada se adjunta a un evento sintético, React agrupará esas mutaciones
  • cuando las mutaciones se agrupan, solo se realiza una única llamada de renderización
  • de lo contrario, cada mutación conduce a una nueva representación

Para permitir el procesamiento por lotes, las mutaciones de su estado deben llamarse dentro de un evento React sintético. Como recordatorio rápido, los eventos React sintéticos son básicamente un envoltorio de eventos HTML nativos para proporcionar la misma funcionalidad en todos los navegadores. Cada controlador de eventos sintético se puede proporcionar directamente a un componente como apoyo.

Para verlo todo en acción, aquí hay un código de ejemplo que permitiría a React combinar todas las mutaciones de estado en un solo renderizado, en lugar de uno por mutación.

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

Actualizaciones por lotes para React 18 y posteriores

Con la próxima versión de React (al menos mientras escribo esto), el mecanismo de procesamiento por lotes será mucho más mejorado. En lugar de ser solo posible en eventos sintéticos, también se podrán utilizar otros tipos de devoluciones de llamada.

  • tiempos muertos
  • promesas
  • controladores de eventos nativos

Es muy probable que este cambio conduzca a un mejor rendimiento de su aplicación, ya que se realizará menos procesamiento automáticamente.

Evitar actualizaciones por lotes en React 18 y posteriores

A partir de React 18, tendrá la opción de activar explícitamente un nuevo renderizado con cada mutación de estado, evitando así una actualización por lotes. Lo más probable es que esto solo se utilice en casos extremos, pero, no obstante, es interesante saberlo.

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