在React中进行批处理的详细指南

267 阅读2分钟

在React中进行批处理

React 18即将推出Automatic Batching ,以减少渲染,同时还有其他新的功能,如SSR对Suspense的支持。对并发功能的支持将有助于改善用户体验。在以前的版本中,React也有批处理功能。但是,随着自动批处理的引入,重新渲染的行为将是统一的。

在React中,如果状态或道具的值发生变化,组件会重新渲染。在类组件中,状态更新可以使用setState 。在功能组件中,状态值可以通过useState 返回的函数进行更新。

React在更新组件状态时执行Batching,以提高性能。Batching意味着将多个状态更新分组为一个重新渲染。让我们看看在v18 之前,Batching是如何工作的,以及在v18 中带来了哪些变化。

React 18之前的批处理

React默认只在事件处理程序中执行批量更新。

因此,setState只在事件处理程序中是异步的。但是,在异步函数中是同步的,比如Promises、setTimeout

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      name: 'setState',
    };
    this.handleClickSync = this.handleClickSync.bind(this);
    this.handleClickAsync = this.handleClickAsync.bind(this);
  }

  handleClickSync() {
    Promise.resolve().then(() => {
      this.setState({ name: 'sync' });
      console.log('state', this.state.name); // sync
    });
  }

  handleClickAsync() {
    this.setState({ name: 'Async' });
    console.log('state', this.state.name); // sync (value of previous state)
    // after re-render state value will be `Async`
  }

  render() {
    return (
      <div>
        <h1 onClick={this.handleClickSync}>Sync setState</h1>
        <h1 onClick={this.handleClickAsync}>Async setState</h1>
      </div>
    );
  }
}

如果n 状态更新出现在异步函数中,React会重新渲染组件n ,每次渲染更新一个状态:

const App = () => {
  const [counter1, setCounter1] = useState(0);
  const [counter2, setCounter2] = useState(0);

  const handleClickWithBatching = () => {
    setCounter1((count) => count + 1);
    setCounter2((count) => count + 1);
  };

  const handleClickWithoutBatching = () => {
    Promise.resolve().then(() => {
      setCounter1((count) => count + 1);
      setCounter2((count) => count + 1);
    });
  };

  console.log('counters', counter1, counter2);
  /* 
    On click of Single re-render
    conuters: 1 1
   
    On click of Multiple re-render
    conuters: 2 1
    counters: 2 2
   */

  return (
    <div className="App">
      <h2 onClick={handleClickWithBatching}>Single Re-render</h2>
      <h2 onClick={handleClickWithoutBatching}>Multiple Re-render</h2>
    </div>
  );
};

然而,强制批处理可以在不稳定的API的帮助下实现ReactDOM.unstable_batchedUpdates

import { unstable_batchedUpdates } from 'react-dom';

const handleClickWithoutBatching = () => {
  Promise.resolve().then(() => {
    unstable_batchedUpdates(() => {
      setCounter1((count) => count + 1);
      setCounter2((count) => count + 1);
    });
  });
};

/* 
 On click of Single re-render
 conuters: 1 1
   
 On click of Multiple re-render
 counters: 2 2
*/
Note: The API is unstable in the sense that React might remove this API once uniformity is brought in the functionality of Batching

React 18中的批处理

React 18在createRoot API的帮助下执行自动批处理:

ReactDOM.createRoot(document.getElementById('root')).render(<App />);

setState是异步的,即使是在异步fucntions里面。

批处理将在整个组件中进行,无论其来源如何:

const handleClick = () => {
  setCounter1((count) => count + 1);
  setCounter2((count) => count + 1);
};

const handleClick = () => {
  Promise.resolve().then(() => {
    setCounter1((count) => count + 1);
    setCounter2((count) => count + 1);
  });
};

//In both cases, component will be re-rendered only once

人们可以选择不使用Batching。ReactDOM.flushSync

import { flushSync } from 'react-dom';

const handleClick = () => {
  flushSync(() => {
    setCounter1((count) => count + 1);
  });
  flushSync(() => {
    setCounter2((count) => count + 1);
  });
};

ReactDOM.unstable_batchedUpdates API在React 18中仍然存在,但它可能会在未来的主要版本中被删除。

学习愉快