使用钩子时,React批状态更新功能是否起作用?

时间:2018-10-29 15:13:12

标签: reactjs react-hooks

对于类组件,如果事件处理程序内部,则this.setState调用batch。但是,如果在事件处理程序外部并使用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

4 个答案:

答案 0 :(得分:16)

TL; DR –如果状态更改是异步触发的(例如,包装在Promise中),则不会进行批量处理;如果直接触发它们,将对其进行批处理。

我已经设置了一个沙箱来尝试: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);

我已经制作了两个按钮,一个按钮触发状态更改(如您的代码示例所示)包装在一个promise中,另一个按钮直接触发状态更改。

如果您查看控制台,则在单击“ promise”按钮时,它将首先显示a aab b,然后显示a aab bb。 / p>

因此答案是否定的,在这种情况下,它不会立即渲染aa - bb,每个状态更改都会触发一个新的渲染,没有批处理。

但是,当您单击“无承诺”按钮时,控制台将立即显示a aab bb

因此,在这种情况下,React会批处理状态更改,并为两者同时进行一个渲染。

答案 1 :(得分:1)

当前在React v16和更早版本中,默认情况下仅批处理React事件处理程序(例如clickonChange等中的更新)。因此,就像类状态更新在钩子中以类似方式进行批处理一样

有一个不稳定的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

答案 2 :(得分:0)

如果事件处理程序为react-based,则它将批处理更新。对于setState或useState调用都是如此。

但是,如果事件基于non-react,即setTimeout,Promise调用,它不会自动进行批处理。简而言之,Web APIs中的任何事件。

答案 3 :(得分:0)

@Patrick Hund 已经给出了答案.. 只是想在这里更新一下,使用 React 18 批处理状态更新可以用于 Promise,默认情况下也可以使用 setTimeout。

<块引用>

直到 React 18,我们只在 React 事件处理程序期间批量更新。默认情况下,React 中不会对 promise、setTimeout、本机事件处理程序或任何其他事件中的更新进行批处理。

查看详细说明。 https://github.com/reactwg/react-18/discussions/21