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

Posted

技术标签:

【中文标题】使用钩子时 React 批处理状态更新功能吗?【英文标题】:Does React batch state update functions when using hooks? 【发布时间】:2019-04-02 13:28:52 【问题描述】:

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

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

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

查看此内容以获取详细说明。 https://github.com/reactwg/react-18/discussions/21

【讨论】:

【参考方案2】:

如果事件处理程序是react-based,那么它会批量更新。对于 setState 或 useState 调用都是如此。

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

【讨论】:

【参考方案3】:

目前在 React v16 和更早的版本中,默认情况下,只有 clickonChange 等 React 事件处理程序内部的更新是批处理的。所以就像类状态更新在钩子中以类似的方式进行批处理

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

【讨论】:

来自github.com/facebook/react/issues/10231#issuecomment-316644950 - This is implementation detail and may change in future versions.的注释 componentDidMount 中的状态更改也是批处理的。 据我所知,React 17.0.1 仍然不会在 React 事件处理程序之外进行批量更新。【参考方案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);

我制作了两个按钮,一个触发包装在您的代码示例中的承诺中的状态更改,另一个直接触发状态更改。

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

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

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

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

【讨论】:

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

以上是关于使用钩子时 React 批处理状态更新功能吗?的主要内容,如果未能解决你的问题,请参考以下文章

反应钩子。无法对未安装的组件执行 React 状态更新

React.js - 功能组件中的状态未更新[重复]

OnSubmit 函数不会更新 React 中的反应钩子状态变量

React hooks 功能组件防止在状态更新时重新渲染

使用 React useState() 钩子更新和合并状态对象

React Context 组件在状态更改时不会更新