Пакетные обновления React

Как объединить обновления состояния в одном вызове рендеринга

Пакетные обновления React.js

Вы уже знаете, что всякий раз, когда вы обновляете состояние компонента, алгоритм различий React обнаруживает изменения в виртуальной DOM и отображает ваш компонент в реальной DOM. Но действительно ли это происходит при каждом обновлении состояния? Или React предоставляет способ пакетной обработки этих обновлений, чтобы несколько мутаций состояния объединялись в один вызов рендеринга?

Синтетические события позволяют обновлять пакеты

React 17 и ниже предоставляет механизм для пакетной обработки этих обновлений. Вам действительно не нужно много думать об этом во время кодирования, но знание того, как React может оптимизировать ваш код, тем не менее, является отличным уроком.

Вот что происходит.

  • множественные мутации состояния вызываются в обратном вызове
  • если обратный вызов привязан к синтетическому событию, React обработает эти мутации
  • когда мутации группируются, выполняется только один вызов рендеринга
  • в противном случае каждая мутация приводит к новому рендерингу

Чтобы разрешить пакетную обработку, мутации вашего состояния должны вызываться внутри синтетического React-события. Напоминаем, что синтетические события React - это, по сути, просто оболочка для собственных HTML-событий, обеспечивающая одинаковую функциональность во всех браузерах. Каждый синтетический обработчик событий может быть предоставлен непосредственно компоненту как свойство.

Чтобы увидеть все это в действии, вот пример кода, который позволит React объединить все мутации состояния в один рендеринг вместо одного рендеринга на мутацию.

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

Пакетные обновления для React 18 и новее

В следующей версии React (по крайней мере, пока я пишу это) механизм пакетной обработки будет намного улучшен. Вместо того, чтобы быть возможными только в синтетических событиях, можно будет использовать и другие типы обратных вызовов.

  • таймауты
  • обещания
  • собственные обработчики событий

Это изменение, скорее всего, приведет к повышению производительности вашего приложения, так как автоматически будет выполняться меньше рендеринга.

Избегайте пакетных обновлений в React 18 и новее

Начиная с React 18, у вас будет возможность явно запускать новый рендеринг при каждой мутации состояния, что позволяет избежать пакетного обновления. Скорее всего, это будет использоваться только в крайних случаях, но, тем не менее, это интересно знать.

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

Предложения

Связанные

Приложение

Языки