【译】深入了解React 18的自动批处理

 

React在事件处理或者内置的hook中,使用批处理合并多个状态的更新。避免了每次的状态更新都要重新渲染组件,从而提高应用的性能。

在React 17以及之前的版本,仅支持浏览器事件的批更新。React 18更新带来增强版的批处理,称为自动批处理Automatic Batching)。它对所有状态的更新,不管来源哪里,都可以使用批处理。

React 17的批处理

如上面提到的,React 17仅支持浏览器event和hook的批处理。因为状态更新只能在异步操作中,因此 原生事件处理不能做批处理。

为了更好地理解这一点,让我们考虑以下在 React 组件中进行状态更新的示例:

import { useState } from "react";
import "./App.css";const App = () => {  const [additionCount, setAdditionCount] = useState(0);
  const [subtractionCount, setSubtractionCount] = useState(0);
  
  console.log("Component Rendering");
  
  const handleOnClick = () => {
    setAdditionCount(additionCount + 1);
    setSubtractionCount(subtractionCount - 1);
  };
    
  return (
     <div>
         <button style = {{ width: "50%", height: "30%" }}
            onClick = {()=>{
               handleOnClick();
             }}
         >
            Click Me!
         </button><div>
            Add Count: {additionCount}
         </div>
         <div>
            Substraction Count: {substractionCount}
         </div></div>
  );
};
export default App;

在上面的示例中,当用户单击按钮时,将调用 handleOnClick() 函数。 它将在每次单击时执行两种状态更新。

如果观察浏览器控制台,将看到两次状态更新只打印一次“Component Rendering”消息。

现在你看到 React 批处理了两个状态的更新,且只重新渲染一次组件。

但是,如果我们在与浏览器无关的上下文中执行状态更新呢?

例如,考虑一个异步加载数据的 fetch() 调用:

// event handler
const handleOnClickAsync = () => {
fetch(“https://jsonplaceholder.typicode.com/todos/1").then(() => {
setAdditionCount(additionCount + 1);
setSubstractionCount(substractionCount — 1);
});
};

// html
<button style={{ width: “50%”, height: “30%” }} 
onClick ={() => {
handleOnClickAsync();
}}
>
Click Me Async Call!
</button>

上面示例是在 fetch() 函数的回调中执行状态更新。 如果执行示例后观察浏览器控制台,将看到 2 条消息。 这表明每次状态更新都会发生两次单独的重新渲染。

这有什么缺点?

对于小型应用,重新渲染过程可能不会产生重大影响。 但是,随着 Web 应用的增长,嵌套组件的数量将会增加。 因此,如果父组件执行非批量状态更新,则每次状态更新都会重新渲染整个组件树。

此行为可能会导致与应用程序速度相关的严重性能问题。 这就是 React 引入自动批处理的原因。

使用自动批处理

React v18 确保默认情况下从任何位置调用的状态更新都会被批处理。 这将批量更新状态,包括native事件处理程序、异步操作、timeout和interval。

让我们考虑一个简单的 React 应用程序来了解自动批处理的工作原理。

首先,让我们使用最新的 React 18 测试版(npm install react@beta react-dom@beta)创建一个 React 项目。

之后,必须更新 index.js 文件以使用 createRoot() API 来启用所有 React 18 功能以使用自动批处理。

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";const container = document.getElementById("root");
// Create a root.
const root = ReactDOM.createRoot(container);
// Render the top component to the root.
root.render(<App />);
reportWebVitals();

现在,让我们用 3 个事件侦听器更新 App.js 文件,其中一个事件侦听器包含使用以下方法调用的状态更新:

  • 事件处理程序(Event Handlers)
  • 异步操作
  • 超时(Timeout)
import logo from "./logo.svg";
import "./App.css";
import { useState } from "react";const App = () => {const [count, setCount] = useState(0);
const [clicked, setClicked] = useState(false);console.log("React 18 Application Re-Rendering");// click event
const handleClick = () => {
  // 1 Re-Render
  setClicked(!clicked);setCount(count + 1); 
};
  
// async operation
const handleAsyncClick = () => {      
  fetch("https://jsonplaceholder.typicode.com/todos/1").then(() => {
    // trigger 1 re-render due to React 18 Improved Batching
    setClicked(!clicked);
    setCount(count + 1);
  });
};
  
// timeout/interval
const handleTimeOutClick = () => {
  setTimeout(() => {
     // trigger 1 re-render due to React 18 Improved Batching
     setClicked(!clicked);
    setCount(count + 1); 
   });
 };
    
 return (
   <div className="App">
     <header className="App-header">
       <div> Count: {count} </div>
       <div> Clicked: {clicked} </div>
       <button onClick={handleClick}> Event Handler </button>
       <button onClick={handleAsyncClick}> Async Handler </button>
       <button onClick={handleTimeOutClick}> Timeout Handler </button>
     </header>
   </div>
 );
};
    
export default App;

当三个按钮都被单击时,浏览器控制台中会打印三个日志,尽管每个事件处理程序中都会发生两个状态更新。

上面的示例显示了一个最佳的重新渲染过程,其中每个事件仅会一次重新渲染,因为 React 批处理所有状态更新,而不管调用位置在哪。

如你所见,React 18 处理组件重新渲染的方式是一种改进。 React 17 有一个空白,非浏览器事件没有被批处理。 React 18 填补了这一空白,并通过减少不必要的重新渲染来提高应用程序性能。

如何停止自动批处理?

自动批处理确实是一个了不起的功能。 但是,在某些情况下,我们需要防止这种情况发生。 为此,React 在 react-dom 中提供了一个名为 flushSync() 的方法,允许我们为特定状态更新触发重新渲染。

flushSync何工作?

要使用flushSync,只需使用以下语法从 react-dom 导入它:

import { flushSync } from 'react-dom';

然后,在事件处理中调用该方法,并将状态更新放在 flushSync() 内。

const handleClick = () => {
flushSync(() => {
setClicked(!clicked);
// react will create a re-render here
});

setCount(count + 1);
// react will create a re-render here
};

当事件被调用时,React 将在 flushSync() 处更新 DOM 并在 setCount(count + 1) 处再次更新 DOM,从而避免批处理。

原文:https://blog.bitsrc.io/automatic-batching-in-react-18-what-you-should-know-d50141dc096e

版权声明:著作权归作者所有。

thumb_up 0 | star_outline 0 | textsms 0