banner
SlhwSR

SlhwSR

热爱技术的一名全栈开发者
github
bilibili

纖維分片

一、問題#

JavaScript 引擎和頁面渲染引擎兩個線程是互斥的,當其中一個線程執行時,另一個線程只能掛起等待。

如果 JavaScript 線程長時間地佔用了主線程,那麼渲染層面的更新就不得不長時間地等待,界面長時間不更新,會導致頁面響應度變差,用戶可能會感覺到卡頓。

而這也正是 React 15Stack Reconciler 所面臨的問題,當 React 在渲染組件時,從開始到渲染完成整個過程是一氣呵成的,無法中斷。

如果組件較大,那麼 js 線程會一直執行,然後等到整棵 VDOM 樹計算完成後,才會交給渲染的線程。

這就會導致一些用戶交互、動畫等任務無法立即得到處理,導致卡頓的情況。

image

二、是什麼#

React Fiber 是 Facebook 花費兩年餘時間對 React 做出的一個重大改變與優化,是對 React 核心算法的一次重新實現。從 Facebook 在 React Conf 2017 會議上確認,React Fiber 在 React 16 版本發布。

react 中,主要做了以下的操作:

  • 為每個增加了優先級,優先級高的任務可以中斷低優先級的任務。然後再重新,注意是重新執行優先級低的任務。
  • 增加了異步任務,調用 requestIdleCallback api,瀏覽器空閒的時候執行。
  • dom diff 樹變成了鏈表,一個 dom 對應兩個 fiber(一個鏈表),對應兩個隊列,這都是為找到被中斷的任務,重新執行。

從架構角度來看,Fiber 是對 React 核心算法(即調和過程)的重寫。

從編碼角度來看,FiberReact 內部所定義的一種數據結構,它是 Fiber 樹結構的節點單位,也就是 React 16 新架構下的虛擬 DOM

一個 fiber 就是一個 JavaScript 對象,包含了元素的信息、該元素的更新操作隊列、類型,其數據結構如下:

type Fiber = {
  // 用於標記fiber的WorkTag類型,主要表示當前fiber代表的組件類型如FunctionComponent、ClassComponent等
  tag: WorkTag,
  // ReactElement裡面的key
  key: null | string,
  // ReactElement.type,調用`createElement`的第一個參數
  elementType: any,
  // The resolved function/class/ associated with this fiber.
  // 表示當前代表的節點類型
  type: any,
  // 表示當前FiberNode對應的element組件實例
  stateNode: any,

  // 指向他在Fiber節點樹中的`parent`,用來在處理完這個節點之後向上返回
  return: Fiber | null,
  // 指向自己的第一個子節點
  child: Fiber | null,
  // 指向自己的兄弟結構,兄弟節點的return指向同一個父節點
  sibling: Fiber | null,
  index: number,

  ref: null | (((handle: mixed) => void) & { _stringRef: ?string }) | RefObject,

  // 當前處理過程中的組件props對象
  pendingProps: any,
  // 上一次渲染完成之後的props
  memoizedProps: any,

  // 該Fiber對應的組件產生的Update會存放在這個隊列裡面
  updateQueue: UpdateQueue<any> | null,

  // 上一次渲染的時候的state
  memoizedState: any,

  // 一個列表,存放這個Fiber依賴的context
  firstContextDependency: ContextDependency<mixed> | null,

  mode: TypeOfMode,

  // Effect
  // 用來記錄Side Effect
  effectTag: SideEffectTag,

  // 單鏈表用來快速查找下一個side effect
  nextEffect: Fiber | null,

  // 子樹中第一個side effect
  firstEffect: Fiber | null,
  // 子樹中最後一個side effect
  lastEffect: Fiber | null,

  // 代表任務在未來的哪個時間點應該被完成,之後版本改名為 lanes
  expirationTime: ExpirationTime,

  // 快速確定子樹中是否有不在等待的變化
  childExpirationTime: ExpirationTime,

  // fiber的版本池,即記錄fiber更新過程,便於恢復
  alternate: Fiber | null,
}

三、如何解決#

Fiber 把渲染更新過程拆分成多個子任務,每次只做一小部分,做完看是否還有剩餘時間,如果有繼續下一個任務;如果沒有,掛起當前任務,將時間控制權交給主線程,在主線程不忙的時候再繼續執行。

即可以中斷與恢復,恢復後也可以複用之前的中間狀態,並給不同的任務賦予不同的優先級,其中每個任務更新單元為 React Element 對應的 Fiber 節點。

實現的上述方式的是 requestIdleCallback 方法。

window.requestIdleCallback() 方法將在瀏覽器的空閒時段內調用的函數排隊。這使開發者能夠在主事件循環上執行後台和低優先級工作,而不會影響延遲關鍵事件,如動畫和輸入響應。

首先 React 中任務切割為多個步驟,分批完成。在完成一部分任務之後,將控制權交回給瀏覽器,讓瀏覽器有時間再進行頁面的渲染。等瀏覽器忙完之後有剩餘時間,再繼續之前 React 未完成的任務,是一種合作式調度。

該實現過程是基於 Fiber 節點實現,作為靜態的數據結構來說,每個 Fiber 節點對應一個 React element,保存了該組件的類型(函數組件 / 類組件 / 原生組件等等)、對應的 DOM 節點等信息。

作為動態的工作單元來說,每個 Fiber 節點保存了本次更新中該組件改變的狀態、要執行的工作。

每個 Fiber 節點有個對應的 React element,多個 Fiber 節點根據如下三個屬性構建一棵樹:

// 指向父級Fiber節點
this.return = null
// 指向子Fiber節點
this.child = null
// 指向右邊第一個兄弟Fiber節點
this.sibling = null

通過這些屬性就能找到下一個執行目標

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。