使用钩子时 React 批处理状态更新功能吗?

IT技术 reactjs react-hooks
2021-04-10 23:58:49

对于类组件,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

4个回答

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中的状态更改,例如您的代码示例,另一个直接触发状态更改。

如果你看一下控制台,当你点击“with promise”按钮时,它会首先显示a aaand b b,然后是a aaand b bb

所以答案是否定的,在这种情况下,它不会立即渲染aa - bb,每次状态变化都会触发新的渲染,没有批处理。

但是,当您点击按钮“不Promise”,控制台将显示a aab bb马上。

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

来自github.com/facebook/react/issues/10231#issuecomment-316644950 的注释-This is implementation detail and may change in future versions.
2021-05-23 23:58:49
我认为@Aprillion 引用的问题不适用于钩子,它是关于类组件的
2021-06-01 23:58:49
顺便说一句,在没有 Promise.resolve 的情况下尝试了它。setA 和 setB 按预期批处理,类似于类组件(在事件处理程序中调用 setState)。
2021-06-09 23:58:49
@ned 虽然问题是在 Hooks 之前创建的,但评论本身适用于任何状态实现,应用程序不应依赖当前的优化细节。
2021-06-14 23:58:49
这个例子中使用的字母非常混乱。也是b b正确的吗?我认为这是一个错字。
2021-06-18 23:58:49

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

有一个不稳定的 API 可以在您需要时在极少数情况下强制在事件处理程序之外进行批处理。

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

有计划在未来版本的 react 可能 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

据我所知,React 17.0.1 仍然不会在 React 事件处理程序之外批量更新。
2021-06-10 23:58:49
来自github.com/facebook/react/issues/10231#issuecomment-316644950 的注释-This is implementation detail and may change in future versions.
2021-06-14 23:58:49
内部的状态更改componentDidMount也被批处理。
2021-06-17 23:58:49

如果事件处理程序是,react-based则它会批量更新。这对于 setState 或 useState 调用都是正确的。

但它不会自动批处理,以防事件non-react基于 setTimeout、Promise 调用。简而言之,来自Web API 的任何事件

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

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

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