Работает ли функция обновления состояния пакета React при использовании хуков?

Для компонентов класса this.setState вызывает пакет, если внутри обработчиков событий. Но что произойдет, если состояние будет обновлено вне обработчика событий и с использованием хука useState?

function Component() {
  const [a, setA] = useState('a');
  const [b, setB] = useState('b');

  function handleClick() {
    Promise.resolve().then(() => {
      setA('aa');
      setB('bb');
    });
  }

  return <button onClick={handleClick}>{a}-{b}</button>
}

Будет ли это сразу aa - bb? Или это будет aa - b а затем aa - bb?

Ответ 1

TL; DR - если изменения состояния инициируются асинхронно (например, заключены в обещание), они не будут упакованы; если они запускаются напрямую, они будут группироваться.

Я установил песочницу, чтобы попробовать это: https://codesandbox.io/s/402pn5l989

import React, { Fragment, useState } from 'react';
import ReactDOM from 'react-dom';

import './styles.css';

function Component() {
  const [a, setA] = useState('a');
  const [b, setB] = useState('b');
  console.log('a', a);
  console.log('b', b);

  function handleClickWithPromise() {
    Promise.resolve().then(() => {
      setA('aa');
      setB('bb');
    });
  }

  function handleClickWithoutPromise() {
    setA('aa');
    setB('bb');
  }

  return (
    <Fragment>
    <button onClick={handleClickWithPromise}>
      {a}-{b} with promise
    </button>
    <button onClick={handleClickWithoutPromise}>
      {a}-{b} without promise
    </button>
      </Fragment>
  );
}

function App() {
  return <Component />;
}

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);

Я сделал две кнопки, одна из которых вызывает изменения состояния, обернутые в обещание, как в вашем примере кода, а другая - непосредственно.

Если вы посмотрите на консоль, когда вы нажмете кнопку "с обещанием", она сначала покажет a aa и bb, затем a aa и b bb.

Таким образом, ответ - нет, в этом случае он не будет визуализировать aa - bb сразу, каждое изменение состояния запускает новый рендеринг, пакетирование не выполняется.

Однако, когда вы нажмете кнопку "без обещания", консоль сразу покажет a aa и b bb.

Таким образом, в этом случае React пакетно изменяет состояние и делает один для обоих вместе.

Ответ 2

В настоящее время в React v16 и более ранних onChange по умолчанию упаковываются только обновления внутри обработчиков событий React, таких как click или onChange т.д. Так же, как обновления состояний классов упакованы аналогичным образом в хуках

Существует нестабильный API для принудительной пакетной обработки вне обработчиков событий в редких случаях, когда это необходимо.

ReactDOM.unstable_batchedUpdates(() => { ... })

Планируется пакетное обновление всех обновлений состояния в будущих версиях на вероятно, v17 или выше.

Теперь также, если вызовы обновления состояния из обработчика событий находятся в асинхронных функциях или вызваны из-за асинхронного кода, они не будут пакетироваться, когда прямые обновления будут пакетироваться

Где без синхронизации состояния кода обновления пакетируются, а обновления асинхронного кода не

function App() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  // async update from useEffect
  useEffect(() => {
    setTimeout(() => {
      setCount1(count => count + 1);
      setCount2(count => count + 2);
    }, 3000);
  }, []);

  const handleAsyncUpdate = async () => {
    await Promise.resolve("state updated");
    setCount1(count => count + 2);
    setCount2(count => count + 1);
  };

  const handleSyncUpdate = () => {
    setCount1(count => count + 2);
    setCount2(count => count + 1);
  };

  console.log("render", count1, count2);
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
      <button type="button" onClick={handleAsyncUpdate}>
        Click for async update
      </button>
      <button type="button" onClick={handleSyncUpdate}>
        Click for sync update
      </button>
    </div>
  );
}

https://codesandbox.io/s/739rqyyqmq

Ответ 3

Если обработчиком события является react-based, он пакетирует обновления. Это верно как для вызовов setState, так и для useState.

Но он не пакетируется автоматически, если событие основано на non-react, то есть setTimeout, Promise вызывает. Короче говоря, любое событие из веб-API.