Fiber 節點 —— 一個資料結構如何承載整個 React 執行期

內存洩漏追查記

之前接手了一個已經跑了兩年的 React 專案。使用者回饋說頁面跑久了會變慢,重新整理後才恢復。Chrome DevTools 的 Memory 面板顯示,每次路由切換後都有大約 15MB 的記憶體無法被回收。15MB 聽起來不多,但使用者的辦公電腦把頁面開上一整天,累積起來就是好幾 GB。

我打開了 Heap Snapshot,準備抓記憶體洩漏。結果讓我愣在原地。

洩漏的物件不是閉包,不是事件監聽器,不是全域變數——而是 FiberNode。成千上萬個 FiberNode 物件躺在 heap 裡,每個大約 300 位元組,但它們像藤蔓一樣互相糾纏,child 指向一個,sibling 指向另一個,return 指向上一個,alternate 指向另一棵樹的對應節點。這些指標織成了一張巨大的網,垃圾回收器在這張網裡迷路了,不知道哪些節點該回收,哪些不該。

我花了三天時間追查這些 FiberNode 的來龍去脈。第二天,我在 ReactFiber.js 裡盯著 FiberNode 建構函式看了整整一個小時。那是一段樸素的程式碼——沒有精巧的演算法,沒有複雜的設計模式,就是一堆 this.xxx = null。但正是這堆賦值語句,定義了 React 執行期世界的全部。

javascript 代碼解讀複製代碼function FiberNode(tag, pendingProps, key, mode) {
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  this.type = null;
  this.stateNode = null;
  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;
  this.ref = null;
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.memoizedState = null;
  this.updateQueue = null;
  this.dependencies = null;
  this.mode = mode;
  this.flags = NoFlags;
  this.subtreeFlags = NoFlags;
  this.lanes = NoLanes;
  this.childLanes = NoLanes;
  this.alternate = null;
  // ...
}

我數了數,二十多個欄位。不多。但每一個欄位都肩負著一項使命——有的描述元件的身分,有的連結樹的結構,有的記錄工作的狀態,有的標記副作用。二十多個欄位的組合,撐起了 React 的全部執行期——元件樹、狀態管理、排程優先級、副作用追蹤、雙緩衝……全部都在這裡。

我關掉 DevTools,喝了口冷掉的咖啡。問題的根因找到了——一個第三方路由函式庫在卸載時忘了斷開 Fiber 樹的根節點引用。三行程式碼的修復。但那兩天裡我對 Fiber 資料結構的理解,比過去一年都深。


一、Virtual DOM 不夠用了

早期的 React(v15 及之前)用 Virtual DOM 來描述畫面。Virtual DOM 是什麼?本質上是 ReactElement 的樹——巢狀的物件結構,每個物件有 typepropschildren

javascript 代碼解讀複製代碼{
  type: 'div',
  props: {
    className: 'container',
    children: [
      { type: 'h1', props: { children: 'Hello' } },
      { type: 'p', props: { children: 'World' } }
    ]
  }
}

這種結構有兩個致命問題。

第一,它只描述了「應該長什麼樣」,沒有描述「要做什麼工作」。 每次 setState,React 需要從頭遍歷整棵樹,比較新舊差異,決定哪些節點要更新。遍歷過程中產生的中間狀態——走到哪了、下一個該去哪、有哪一些副作用需要執行——全部存在 JavaScript 的呼叫堆疊上。結果就是遍歷一旦開始就無法中斷,因為呼叫堆疊裡的資訊在函式返回後就丟失了。

第二,它是不可變的。 每次更新都建立一棵全新的樹。舊樹丟棄,新樹接管。這對於簡單的應用沒有問題,但對於需要增量更新的場景(如動畫、高頻率輸入),效能代價巨大。

Fiber 資料結構就是為了解決這兩個問題而生的。它的核心設計判斷是:節點不應該只描述 UI,還應該描述工作。每個 Fiber 節點就是一個工作單元——它知道自己是什麼類型的元件、連結到哪些上下游節點、有什麼待處理的更新、屬於什麼優先級、需要什麼副作用。

這是資料結構層面的典範轉移——從「描述 UI 的樹」到「描述工作的鏈結串列」。


二、Fiber 節點的四重身分

打開 packages/react-reconciler/src/ReactInternalTypes.js,看看 Fiber 類型的完整定義:

javascript 代碼解讀複製代碼// https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactInternalTypes.js
export type Fiber = {
  // ===== 第一重身分:元件描述 =====
  tag: WorkTag,              // 元件類型標記(函式/類別/Host/文字...)
  key: null | string,        // React key
  elementType: any,          // ReactElement.type(未解析)
  type: any,                 // 解析後的元件(函式引用/類別/字串標籤)
  stateNode: any,            // 關聯的真實實例(DOM 節點/元件實例)

  // ===== 第二重身分:樹的結構 =====
  return: Fiber | null,      // 父節點
  child: Fiber | null,       // 第一個子節點
  sibling: Fiber | null,     // 下一個兄弟節點
  index: number,             // 在父節點 children 中的位置

  // ===== 第三重身分:工作狀態 =====
  pendingProps: any,         // 新的 props(待處理)
  memoizedProps: any,        // 上一次 render 使用的 props
  memoizedState: any,        // 上一次 render 使用的 state
  updateQueue: mixed,        // 狀態更新佇列
  dependencies: Dependencies | null,  // Context 依賴
  mode: TypeOfMode,          // 執行模式
  lanes: Lanes,              // 本節點的工作優先級
  childLanes: Lanes,         // 子樹的工作優先級

  // ===== 第四重身分:副作用追蹤 =====
  flags: Flags,              // 本節點的副作用
  subtreeFlags: Flags,       // 子樹的副作用
  deletions: Array<Fiber> | null,  // 需要刪除的子節點
  alternate: Fiber | null,   // 雙緩衝中的對應節點
  ref: any,                  // ref 引用
};

這四個維度的組合,讓一個 Fiber 節點同時扮演了四種角色:

維度 對應欄位 回答的問題
元件描述 tag, key, elementType, type, stateNode 「我是誰?」
樹的結構 return, child, sibling, index 「我在哪?」
工作狀態 pendingProps, memoizedProps, memoizedState, updateQueue, lanes 「我要做什麼?」
副作用追蹤 flags, subtreeFlags, deletions, alternate 「我做完了要通知誰?」

每一個欄位都是執行期某個功能的必要載體。刪掉任何一個欄位,React 的某個功能就會損壞。alternate 刪掉,雙緩衝沒了。lanes 刪掉,並發排程沒了。flags 刪掉,DOM 更新不知道要做什麼。


三、從原始碼裡讀懂每個欄位

3.1 ReactWorkTags.js —— 32 種 Fiber 的身分牌

先看看 tag 欄位。它不是隨便一個數字,而是來自 ReactWorkTags.js 的列舉:

javascript 代碼解讀複製代碼// https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactWorkTags.js
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const HostRoot = 3;           // 樹根節點
export const HostPortal = 4;         // Portal 子樹入口
export const HostComponent = 5;      // DOM 元素(div, span...)
export const HostText = 6;           // 文字節點
export const Fragment = 7;
export const Mode = 8;               // StrictMode / ConcurrentMode
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const ScopeComponent = 21;
export const OffscreenComponent = 22;
export const LegacyHiddenComponent = 23;
export const CacheComponent = 24;
export const TracingMarkerComponent = 25;
export const HostHoistable = 26;     // 可提升資源(link preload)
export const HostSingleton = 27;     // 單例元素(html, head, body)
export const IncompleteFunctionComponent = 28;
export const Throw = 29;             // 用於拋出錯誤的特殊元件
export const ViewTransitionComponent = 30;
export const ActivityComponent = 31;

32 種型別。從最常見的 FunctionComponent(0)到專門用於錯誤處理的 Throw(29),從代表 DOM 元素的 HostComponent(5)到伺服器端渲染啟用相關的 DehydratedFragment(18)。每種型別在 beginWorkcompleteWork 中都有自己的處理邏輯。

tag 欄位是 reconciler 的「路由表」。當 beginWork 處理一個 Fiber 節點時,第一個判斷就是 switch (fiber.tag)——不同 tag 走不同的分支。函式元件需要呼叫函式取得子節點,類別元件需要呼叫 render 方法,Host 元件需要建立 DOM 元素,Suspense 元件需要檢查是否已 resolve……路由決策完全基於這個小小的整數。

3.2 FiberNode 建構函式 —— 二十多個欄位的誕生現場

看看 ReactFiber.js 中的 FiberNode 建構函式:

javascript 代碼解讀複製代碼// https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiber.js
function FiberNode(tag, pendingProps, key, mode) {
  // --- Instance(實例標識) ---
  this.tag = tag;
  this.key = key;
  this.elementType = null;    // ReactElement.type(未解析的原始值)
  this.type = null;           // 解析後的值(函式引用/類別建構函式/DOM 標籤字串)
  this.stateNode = null;      // 關聯的真實平台實例

  // --- Fiber(鏈結串列指標) ---
  this.return = null;         // 父 Fiber
  this.child = null;          // 第一個子 Fiber
  this.sibling = null;        // 下一個兄弟 Fiber
  this.index = 0;             // 在父節點 children 中的位置索引
  this.ref = null;            // ref 引用
  this.refCleanup = null;     // ref 清理函式(useImperativeHandle 等)

  // --- Input / Output(輸入輸出) ---
  this.pendingProps = pendingProps;  // 新的 props
  this.memoizedProps = null;         // 上一次 render 使用的 props
  this.updateQueue = null;           // 更新佇列(state、callbacks)
  this.memoizedState = null;         // 上一次 render 使用的 state
  this.dependencies = null;          // Context 依賴列表

  // --- Mode(執行模式) ---
  this.mode = mode;           // ConcurrentMode / StrictMode / NoMode

  // --- Effects(副作用標記) ---
  this.flags = NoFlags;               // 本節點需要執行的副作用
  this.subtreeFlags = NoFlags;        // 子樹中需要執行的副作用
  this.deletions = null;              // 需要刪除的子節點列表

  // --- Scheduling(排程) ---
  this.lanes = NoLanes;       // 本節點需要處理的工作的優先級
  this.childLanes = NoLanes;  // 子樹需要處理的工作的優先級

  // --- Alternate(雙緩衝) ---
  this.alternate = null;      // 指向另一棵樹的對應節點

  // --- Profiler(效能分析,DEV 模式) ---
  if (enableProfilerTimer) {
    this.actualDuration = 0;
    this.actualStartTime = -1;
    this.selfBaseDuration = 0;
    this.treeBaseDuration = 0;
  }
}

這段程式碼樸素得讓人驚訝。沒有繼承鏈,沒有複雜的初始化邏輯,就是一個函式,一堆 this.xxx = yyy。但正是這種樸素,賦予了 Fiber 節點極高的靈活性——它是一個純資料物件,可以被任意複製、修改、丟棄、重建,而不需要擔心隱藏的副作用。

注意 elementTypetype 的差別——這兩個欄位經常讓人困惑:

欄位 值(函式元件) 值(類別元件) 值(DOM 元素)
elementType 函式引用(App 類別建構函式(MyComponent 字串('div'
type 同上 同上 同上

大多數情況下它們相等。但在 ForwardRefLazy 元件中,elementTypetype 不同——elementType 是外層包裝(REACT_FORWARD_REF_TYPE),type 是內層的真實元件。Reconciler 用 elementType 判斷是否需要重新渲染(比較 key 和 type),用 type 執行實際的元件呼叫。

3.3 鏈結串列指標 —— child / sibling / return

這三個欄位是 Fiber 架構的靈魂。它們把一個階層化的元件樹,轉換成了一個可以遍歷、中斷、恢復的鏈結串列結構。

child:指向第一個子節點。如果一個元件有多個子節點,只能透過 child 找到第一個,然後透過 sibling 鏈遍歷其餘的。

sibling:指向下一個兄弟節點。child → sibling → sibling → null 這條鏈,完整地表示了父節點的所有子節點。

return:指向父節點。為什麼叫 return 不叫 parent?因為這是從遍歷視角命名的——處理完當前節點後,要「返回到」哪個節點繼續處理。在深度優先遍歷中,處理完一個葉節點後,要 return 到它的父節點,然後檢查父節點的 sibling。return 比 parent 更精確地表達了這個語意。

這三個指標的組合,讓 Fiber 樹可以表達任意複雜的巢狀結構——但遍歷它不需要遞迴,只需要一個 while 迴圈和一個當前指標。遍歷過程中的「位置信息」完全保存在物件圖裡,不在呼叫堆疊上。這就是 Fiber 可以中斷和恢復的根本原因。

3.4 lanes 與 childLanes —— 優先級傳播的管道

laneschildLanes 是 Fiber 並發排程的核心欄位。它們都是 31 位元的整數,用位元遮罩表示不同的更新優先級。

  • lanes:這個 Fiber 節點自身有待處理的工作。比如使用者在這個元件上呼叫了 setState,它的 lanes 欄位會被標記上對應的優先級。
  • childLanes:這個 Fiber 節點的子樹中有待處理的工作。如果子樹深處的某個元件有更新,這個更新資訊會透過 childLanes 向上傳播到所有祖先節點。

為什麼需要 childLanes?因為 workLoop 需要快速判斷「一棵子樹裡有沒有工作要做」。如果沒有 childLanes,workLoop 需要遍歷整棵子樹才能確定是否有待處理的工作——這在大型應用中不可接受。有了 childLanes,workLoop 只需要檢查根節點的 childLanes——如果為 0,整棵樹都沒有工作;如果非 0,沿著標記了優先級的分支向下找。

Counter 呼叫了 setStateCounter.lanes 被標記為 DefaultLane。這個標記向上傳播——Counter.returnApp)的 childLanes 被標記,App.returnHostRoot)的 childLanes 也被標記。當排程器檢查是否有工作時,它看到 HostRoot.childLanes !== 0,就知道子樹裡有工作要做。但它不需要檢查 Header 分支——因為 App.childLanes 只標記了 Counter 所在的分支,HeaderchildLanes 為 0,可以被安全跳過。

3.5 flags 與 subtreeFlags —— 副作用的收集器

flagssubtreeFlags 是 Fiber 副作用系統的核心。它們也是位元遮罩,每一位代表一種副作用類型:

javascript 代碼解讀複製代碼// https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberFlags.js
export const NoFlags = /*                      */ 0b000000000000000000000000000;
export const Placement = /*                    */ 0b000000000000000000000000010;
export const Update = /*                       */ 0b000000000000000000000000100;
export const PlacementAndUpdate = /*           */ 0b000000000000000000000000110;
export const Deletion = /*                     */ 0b000000000000000000000001000;
export const ChildDeletion = /*                */ 0b000000000000000000000010000;
export const ContentReset = /*                 */ 0b000000000000000000000100000;
export const Callback = /*                     */ 0b000000000000000000001000000;
export const DidCapture = /*                   */ 0b000000000000000000010000000;
export const ForceClientRender = /*            */ 0b000000000000000000100000000;
export const Ref = /*                          */ 0b000000000000000001000000000;
export const Snapshot = /*                     */ 0b000000000000000010000000000;
export const Passive = /*                      */ 0b000000000000000100000000000;
export const Visibility = /*                   */ 0b000000000000001000000000000;
export const StoreConsistency = /*             */ 0b000000000000010000000000000;
export const HostEffectMask = /*               */ 0b000000000000011111111111111;
export const Hydrating = /*                    */ 0b000000000000100000000000000;
export const BeforeMutationMask = /*           */ 0b000000000000010000000100100;
export const MutationMask = /*                 */ 0b000000000000001010000010110;
export const LayoutMask = /*                   */ 0b000000000000000001000001000;
export const PassiveMask = /*                  */ 0b000000000000000100000000000;
Flag 含義
Placement 這是一個新節點,需要插入到 DOM 中
Update 節點的 props 或 state 發生了變化,需要更新
Deletion 節點需要被刪除
ChildDeletion 子節點中有被刪除的(用於清理 refs)
Ref 節點的 ref 需要更新(attach 或 detach)
Snapshot 類別元件的 getSnapshotBeforeUpdate 需要呼叫
Passive useEffect 回呼需要執行
Visibility Offscreen 元件的可見性發生了變化

flags 標記本節點的副作用。當 beginWork 處理一個 Fiber 節點時,如果發現它需要被插入(新建立的節點)或更新(props 變化了),就在 flags 欄位上打上對應的位元。

subtreeFlags 標記子樹中所有副作用的按位或。completeWork 在「完成」一個節點時(也就是它的所有子節點都已處理完畢),會把子節點的 flagssubtreeFlags 合併到自己的 subtreeFlags 中。這樣,當遍歷到達根節點時,subtreeFlags 包含了整棵樹的所有副作用類型——commit 階段只需要檢查根節點的 subtreeFlags,就能決定需要執行哪些階段的副作用處理。

deletions 欄位是一個特殊的陣列。當一個子節點需要被刪除時,它不是在自己的 flags 上標記 Deletion——而是被放入父節點的 deletions 陣列中。為什麼?因為被刪除的節點不會再參與後續的遍歷,如果副作用標記在它自己身上,commit 階段可能找不到它。放入父節點的 deletions 陣列,確保了 commit 階段能可靠地執行清理操作。

3.6 alternate —— 雙緩衝的橋樑

alternate 是 Fiber 雙緩衝機制的核心欄位。每個 Fiber 節點都有一個 alternate,指向另一棵樹(current 或 workInProgress)中的對應節點。

當 React 開始一次新的渲染時,它會從 current 樹的根節點出發,透過 createWorkInProgress 建立 workInProgress 樹。這個過程中,每建立一個新的 workInProgress Fiber,就透過 alternate 欄位和 current Fiber 雙向連結:

javascript 代碼解讀複製代碼// https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiber.js
workInProgress.alternate = current;
current.alternate = workInProgress;

alternate 連結帶來的好處是巨大的:

  1. 比較新舊狀態:render 階段可以隨時透過 workInProgress.alternate.memoizedProps 取得上一次 render 的 props,決定是否需要重新渲染。
  2. 重用節點createWorkInProgress 優先重用 alternate 節點,減少物件建立和垃圾回收壓力。
  3. 快速切換:commit 階段只需要交換根節點的指標,current 變成 workInProgressworkInProgress 變成舊的 current。原子操作,沒有中間狀態。

四、根節點:FiberRootNode 與 RootFiber 的差別

我一直有一個問題:createRoot(container) 建立的「根」到底是個什麼東西?為什麼有兩個「根」的概念?

React 的「根」實際上是一個雙層結構

FiberRootNode(根容器):由 createFiberRoot 建立。它不是 Fiber,是一個獨立的物件型別。它管理整個 React 應用的全域狀態——containerInfo(容器 DOM 節點)、pendingLanes(所有待處理的優先級)、callbackNode(排程回呼)、onUncaughtError / onCaughtError(錯誤處理回呼)。一個 React 應用通常只有一個 FiberRootNode。

RootFiber(根 Fiber):是一個 tag = HostRoot 的 Fiber 節點。它是 Fiber 樹的入口點——所有元件都是它的後代。它有 alternate,參與雙緩衝。RootFiber 的 stateNode 指向 FiberRootNode,FiberRootNode 的 current 指向 current 樹的 RootFiber。

為什麼要兩層?因為 FiberRootNode 不參與 Fiber 樹的遍歷和雙緩衝。它的欄位(如 pendingLanescallbackNode)是全域性的,不需要「兩個版本」。如果把所有欄位都放在 RootFiber 上,雙緩衝時就需要複製這些全域狀態,既浪費記憶體又容易出錯。分層設計讓「全域狀態」和「樹節點」各自歸位。

javascript 代碼解讀複製代碼// https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactInternalTypes.js
export type FiberRoot = {
  // 容器資訊
  tag: RootTag,                          // LegacyRoot / ConcurrentRoot
  containerInfo: Container,              // DOM 容器節點
  // 樹指標
  current: Fiber,                        // 指向 current 樹的 RootFiber
  // 排程狀態
  callbackNode: mixed,                   // Scheduler 的回呼把手
  callbackPriority: Lane,                // 當前回呼的優先級
  pendingLanes: Lanes,                   // 所有待處理的 lane
  expiredLanes: Lanes,                   // 已過期的 lane(必須同步執行)
  // 事件處理
  onUncaughtError: (error: mixed, errorInfo: {...}) => void,
  onCaughtError: (error: mixed, errorInfo: {...}) => void,
  onRecoverableError: (error: mixed, errorInfo: {...}) => void,
  // ... 更多欄位
};

五、為一個複雜執行期設計核心資料結構

位元遮罩是效能優化的利器

Fiber 的 lanesflagssubtreeFlags 都是位元遮罩。位元運算在 JavaScript 中雖然有 32 位元限制,但對於表達「多選狀態」(一個節點可以有多個副作用、多個優先級)來說,位元遮罩比陣列或物件更有效率:

  • 按位或|)新增標記:flags |= Placement
  • 按位與&)檢查標記:if (flags & Placement) { ... }
  • 按位非~)和與(&)清除標記:flags &= ~Placement
  • 按位與&)提取最低位的 1:lanes & -lanes 取得最高優先級

這些操作都是 O(1) 的,在現代 JavaScript 引擎中可以被高度最佳化。

好的資料結構能讓團隊平行工作

Fiber 資料結構的四重身分分離——元件描述、樹結構、工作狀態、副作用追蹤——讓不同的團隊成員可以專注於不同的領域:

  • 負責元件模型的工程師關注 tagtypememoizedState
  • 負責排程系統的工程師關注 laneschildLanes
  • 負責渲染的工程師關注 flagssubtreeFlagsstateNode
  • 負責 DevTools 的工程師關注 alternateref

這些欄位共同定義在 Fiber 類型中,構成了團隊的共享契約。每個人都在同一個資料結構上工作,但關注點不同。

React Fiber 的設計 遷移策略
四重身分分離(元件/樹/工作/副作用) 核心資料實體按職責維度拆分欄位組,每個團隊負責一組
位元遮罩表達多選狀態 狀態和標記用位元運算管理,減少陣列遍歷和物件巢狀
childLanes 向上傳播 樹狀結構中需要「快速判斷子樹是否有工作」時,用聚合欄位避免全量遍歷
alternate 雙緩衝 任何需要「準備新版本再原子切換」的場景,維護兩個版本的並行結構
FiberRootNodeRootFiber 分層 全域狀態與樹節點狀態分離,避免雙緩衝時複製全域資料

六、二十多個欄位,撐起一個世界

回到那個 FiberNode 建構函式。

javascript 代碼解讀複製代碼function FiberNode(tag, pendingProps, key, mode) {
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  this.type = null;
  this.stateNode = null;
  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;
  this.ref = null;
  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.memoizedState = null;
  this.updateQueue = null;
  this.dependencies = null;
  this.mode = mode;
  this.flags = NoFlags;
  this.subtreeFlags = NoFlags;
  this.lanes = NoLanes;
  this.childLanes = NoLanes;
  this.alternate = null;
}

二十多個欄位。沒有魔法,沒有黑科技。但它們的組合方式,支撐了現代前端框架中最複雜的執行期系統之一。

tag 決定了「我是誰」。child / sibling / return 決定了「我在哪裡,和誰連結」。pendingProps / memoizedState / updateQueue 決定了「我要做什麼」。lanes / childLanes 決定了「什麼時候做」。flags / subtreeFlags / deletions 決定了「做完之後要通知誰」。alternate 決定了「如果被打斷了,怎麼恢復到剛才的狀態」。

這些欄位的組合,讓 React 能夠:在 16 毫秒的幀間隙裡穿插多個不同優先級的更新;在渲染過程中回應使用者輸入而不丟失位置信息;在 commit 之前安全地丟棄不完整的工作;在 DOM 操作完成後再呼叫生命週期方法。

一個好的資料結構,就像一個好的建築結構——它不顯眼,但它決定了整個系統的可能性和上限。Fiber 資料結構是 React 的骨架。Hooks、Concurrent Mode、Suspense、Server Components——這些華麗的上層建築,都站在這二十多個欄位的肩膀之上。


原文出處:https://juejin.cn/post/7659763781161730102


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝18   💬3   ❤️5
745
🥈
我愛JS
📝2   💬5   ❤️3
142
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登