# 1.自动批处理

批处理是指 React 将多个状态更新合并为单个重新渲染,以提高性能。

在 React 17 及之前版本中,只在 React事件处理函数 中进行批处理更新,而在其他情况下(如 promise、setTimeout、原生事件处理程序等)则不会批处理。

举个例子,React 18 之前,渲染次数和更新次数是一样的,即使我们更新了两个状态,每次更新组件也只渲染一次。(代码如下)

import React, { useState } from 'react';

// React 18 之前
const App: React.FC = () => {
  console.log('App组件渲染了!');
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  return (
    <button
      onClick={() => {
        setCount1(count => count + 1);
        setCount2(count => count + 1);
        // 在React事件中被批处理
      }}
    >
      {`count1 is ${count1}, count2 is ${count2}`}
    </button>
  );
};

export default App;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

但是,如果我们把状态的更新放在 promise 或者 setTimeout 里面:

每次点击更新两个状态,组件都会渲染两次,不会进行批量更新。(代码如下)

import React, { useState } from 'react';

// React 18 之前
const App: React.FC = () => {
  console.log('App组件渲染了!');
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  return (
    <div
      onClick={() => {
        setTimeout(() => {
          setCount1(count => count + 1);
          setCount2(count => count + 1);
        });
        // 在 setTimeout 中不会进行批处理
      }}
    >
      <div>count1: {count1}</div>
      <div>count2: {count2}</div>
    </div>
  );
};

export default App;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

在原生js事件中,情况也是一样,点击更新两个状态,组件都会渲染两次,不会进行批量更新。(代码如下)

import React, { useEffect, useState } from 'react';

// React 18 之前
const App: React.FC = () => {
  console.log('App组件渲染了!');
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  useEffect(() => {
    document.body.addEventListener('click', () => {
      setCount1(count => count + 1);
      setCount2(count => count + 1);
    });
    // 在原生js事件中不会进行批处理
  }, []);
  return (
    <>
      <div>count1: {count1}</div>
      <div>count2: {count2}</div>
    </>
  );
};

export default App;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

TIP

在 React 18 上面的三个例子只会有一次 render,因为所有的更新都将自动批处理。这样无疑是很好的提高了应用的整体性能。

# 不过这种情况会在 React 18 中执行两次 render:

await 会破坏自动批处理:任何异步操作(await/Promise/then)都会打断批处理

import React, { useState } from 'react';

// React 18
const App: React.FC = () => {
  console.log('App组件渲染了!');
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);
  return (
    <div
      onClick={async () => {
        await setCount1(count => count + 1);
        setCount2(count => count + 1);
      }}
    >
      <div>count1: {count1}</div>
      <div>count2: {count2}</div>
    </div>
  );
};

export default App;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  • 注意:flushSync 函数内部的多个 setState 仍然为批量更新,这样可以精准控制哪些不需要的批量更新。
  • 主要优势
    • 减少不必要的渲染:合并多个状态更新为单个重新渲染
    • 提高性能:减少组件渲染次数,特别是对于复杂应用
    • 更一致的更新行为:无论在什么环境下触发更新,行为都一致

# 2.并发模式(Concurrent Mode)

并发模式是 React 的一种新渲染机制,它允许 React 同时准备多个版本的 UI,并根据优先级选择性地渲染。这与传统的同步渲染(一次完成整个树更新)有本质区别。

React 17 和 React 18 的区别就是:从同步不可中断更新变成了异步可中断更新。

# 核心概念

    1. 并发渲染(Concurrent Rendering)
    • 可中断渲染:React 可以在渲染过程中暂停、继续或放弃工作
    • 优先级调度:区分高优先级(如用户输入)和低优先级更新(如数据加载)
    • 时间切片:将渲染工作分成小块,避免长时间阻塞主线程
    1. 并发特性(Concurrent Features)

    React 18 提供了一系列基于并发模式的新API:

    • useTransition:标记非紧急更新
    • useDeferredValue:延迟更新某些值
    • <Suspense>:更好地控制加载状态

# 实际应用场景

  • 输入响应优化:确保输入框即时响应,同时后台处理复杂计算
  • 路由过渡:切换路由时保持当前界面响应,后台加载新路由
  • 大数据渲染:分批渲染大型列表,避免界面冻结
  • 依赖加载:优雅处理代码分割和动态导入

# 启用并发模式

从React 18开始,默认启用部分并发特性,完整启用方式:

import { createRoot } from 'react-dom/client';

// 替换原来的ReactDOM.render
createRoot(document.getElementById('root')).render(<App />);
1
2
3
4

# 3.关于 React 组件的返回值

在 React 17 中,如果你需要返回一个空组件,React只允许返回null。如果你显式的返回了 undefined,控制台则会在运行时抛出一个错误。

在 React 18 中,不再检查因返回 undefined 而导致崩溃。既能返回 null,也能返回 undefined(但是 React 18 的dts文件还是会检查,只允许返回 null,你可以忽略这个类型错误)。

# 4.新的 Hook

# useId

useId 是一个新的Hook,用于生成在客户端和服务端两侧都独一无二的 id,避免激活后两侧内容不匹配。它主要用于需要唯一 id 的,具有集成 API 的组件库。

这个更新不仅解决了一个在 React 17 及更低版本中的存在的问题,而且它会在 React 18 中发挥更重要的作用,因为新的流式服务端渲染响应 HTML 的方式将是无序的,需要独一无二的 id 作为索引。

# useDeferredValue

useDeferredValue 允许推迟渲染树的非紧急更新。这和防抖操作非常相似,但是有一些改进。它没有固定的延迟时间,React 会在第一次渲染在屏幕上出现后立即尝试延迟渲染。延迟渲染是可中断的,它不会阻塞用户输入。

# useSyncExternalStore

useSyncExternalStore 是一个新的 Hook,允许使用第三方状态管理来支持并发模式,并且能通过对 store 进行强制更新实现数据同步。对第三方数据源的订阅能力的实现上,消除了对 useEffect 的依赖,推荐任何 React 相关的第三方状态管理库使用这个新特性。

lastUpdate: 5/4/2025, 12:01:19 PM