iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0
JavaScript

React 觀念架構:從js 基礎到Hook 底層邏輯 系列 第 23 篇

Day 23 State 更新隊列與 Batching(批次更新)

  • 分享至 

  • xImage
  •  

我們深入解析了 Render 階段與 Commit 階段的分工。今天我們要討論 React 處理狀態變更的核心底層:UpdateQueue(狀態更新隊列) 與 Automatic Batching(自動批次更新) 機制

一、 Update 物件與 UpdateQueue 鏈表

當你在組件中呼叫 setState 時,React 並不會「立刻」更新狀態,而是會在背後建立一個 Update 物件 並掛載到 Fiber 節點的 UpdateQueue 鏈表中。

  1. Update 物件結構

    const update = {
      lane: lane,             // 優先級標記 (Bitmask)
      action: action,         // newState 或 (prevState) => newState 函數
      hasEagerState: false,   // 是否可提前計算出 State (Eager State 優化)
      eagerState: null,
      next: null,             // 指向下一個 Update (形成單向環狀鏈表)
    };
    
  2. 更新隊列的合併(Base State + Updates = New State)
    在 Render 階段走訪到該 Fiber 時,processUpdateQueue 函數會從 baseState 出發,依次遍歷 UpdateQueue 鏈表中的更新:

    • 若更新的 lane 符合當前渲染的優先級,則將其計算並累加到最新狀態。

    • 若優先級不足,該 Update 會被跳過 (Skip),並保留至下一次低優先級渲染時再重新計算。

二、 什麼是 Batching(批次更新)?

Batching 是 React 的一種效能優化策略:將多個 setState 操作合併為一次 Render 過程執行,避免多次重複觸發重新渲染。

// 在一個事件處理函數中連續觸發多次 setState
const handleClick = () => {
  setCount(c => c + 1);
  setFlag(f => !f);
  // React 會將這兩次 setState 合併,只觸發一次 Re-render!
};

三、 React 18 之前的痛點:Legacy Batching

在 React 17 及更早的版本中,批次更新依賴於 batchedUpdates 函數包裹。React 預設只會在 React 事件監聽器(如 onClick、onChange) 內部自動開啟 Batching。

舊版的悲劇情境:非同步環境下的 Batching 失效

只要脫離了 React 事件的回調環境,Batching 就會直接失效,導致「每一次 setState 都同步觸發一次 Re-render」:

// React 17 及更早版本
const handleClick = () => {
  fetchData().then(() => {
    //  位於 Promise / setTimeout 非同步微任務中
    setCount(c => c + 1); // 觸發第 1 次 Re-render 
    setFlag(f => !f);     // 觸發第 2 次 Re-render 
  });
};

四、 React 18 的救星:Automatic Batching(全自動批次更新)

React 18 引進了全新的根節點建立 API createRoot,並正式啟用 Automatic Batching。

關鍵轉變:

在 React 18 中,無論 setState 發生在哪裡(包含 Promise、setTimeout、原生 DOM 事件處理器、IntersectionObserver 等),React 都會自動統一進行批次處理!

// React 18 自動批次更新
const handleClick = () => {
  setTimeout(() => {
    setCount(c => c + 1);
    setFlag(f => !f);
    // 即使在 setTimeout 內部,React 18 依然將兩者合併,只觸發 1 次 Re-render! 
  }, 1000);
};

五、 如何在 React 18 中強制「同步更新」?

有些極端情況下(例如需要在 setState 後立刻讀取最新的 DOM 幾何尺寸),如果你不希望被自動 Batching,可以使用 React 提供的 flushSync:

import { flushSync } from 'react-dom';

const handleClick = () => {
  flushSync(() => {
    setCount(c => c + 1); // 立刻觸發 DOM 寫入與渲染
  });
  // 此時 DOM 已經更新完畢
  flushSync(() => {
    setFlag(f => !f);     // 立刻觸發二次渲染
  });
};

上一篇
Day 22 Render 階段與 Commit 階段:Reconciler 與 Renderer 如何協作?
下一篇
Day 24 Diffing 演算法拆解:單節點與多節點 Diff 的啟發式策略
系列文
React 觀念架構:從js 基礎到Hook 底層邏輯 共 25 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言