React16源碼解析(三)-ExpirationTime

React源碼解析系列文章歡迎閱讀:
React16源碼解析(一)- 圖解Fiber架構
React16源碼解析(二)-創建更新
React16源碼解析(三)-ExpirationTime
React16源碼解析(四)-Scheduler
React16源碼解析(五)-更新流程渲染階段1
React16源碼解析(六)-更新流程渲染階段2
React16源碼解析(七)-更新流程渲染階段3
React16源碼解析(八)-更新流程提交階段
正在更新中...

在我的上篇文章中,ReactDOM.render過程中的updateContainer函數裏面有個計算到期時間的函數:

export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): ExpirationTime {
  const current = container.current;
  const currentTime = requestCurrentTime();
  // 這裏傳入了currentTime和當前的Fiber對象調用了這個計算expirationTime的函數
  const expirationTime = computeExpirationForFiber(currentTime, current);
  return updateContainerAtExpirationTime(
    element,
    container,
    parentComponent,
    expirationTime,
    callback,
  );
}

在上篇文章討論的setState和forceUpdate中同樣的也要計算expirationTime,這篇文章就來分析expirationTime是如何計算的。

爲什麼需要ExpirationTime?

React16帶來的最振奮人心的改動就是Fiber架構,改變了之前react的組件渲染機制,新的架構使原來同步渲染的組件現在可以異步化,可中途中斷渲染,執行更高優先級的任務。釋放瀏覽器主線程。

所以每一個任務都會有一個優先級,不然豈不是會亂套了..... ExpirationTime就是優先級,它是一個過期時間。

computeExpirationForFiber

在計算ExpirationTime之前調用了requestCurrentTime得到了一個currentTime。這個函數裏面牽扯了一些複雜的關於後面知識的邏輯,我們先不深究,大家就先理解爲一個當前時間類似的概念。

function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
  let expirationTime;
    // ......
    if (fiber.mode & ConcurrentMode) {
      if (isBatchingInteractiveUpdates) {
        // 交互引起的更新 
        expirationTime = computeInteractiveExpiration(currentTime);
      } else {
        // 普通異步更新
        expirationTime = computeAsyncExpiration(currentTime);
      }
    } 
    // ......
  }
  // ......
  return expirationTime;
}

在異步更新中,這裏我們看到有兩種計算更新的方式。computeInteractiveExpiration和computeAsyncExpiration

computeInteractiveExpiration

export const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150;
export const HIGH_PRIORITY_BATCH_SIZE = 100;

export function computeInteractiveExpiration(currentTime: ExpirationTime) {
  return computeExpirationBucket(
    currentTime,
    HIGH_PRIORITY_EXPIRATION,//150
    HIGH_PRIORITY_BATCH_SIZE,//100
  );
}

computeAsyncExpiration

export const LOW_PRIORITY_EXPIRATION = 5000;
export const LOW_PRIORITY_BATCH_SIZE = 250;

export function computeAsyncExpiration(
  currentTime: ExpirationTime,
): ExpirationTime {
  return computeExpirationBucket(
    currentTime,
    LOW_PRIORITY_EXPIRATION,//5000
    LOW_PRIORITY_BATCH_SIZE,//250
  );
}

computeExpirationBucket

查看上面兩種方法,我們發現其實他們調用的是同一個方法:computeExpirationBucket,只是傳入的參數不一樣,而且傳入的是常量。computeInteractiveExpiration傳入的是150、100,computeAsyncExpiration傳入的是5000、250。說明前者的優先級更高。那麼我把前者稱爲高優先級更新,後者稱爲低優先級更新。

下面來看computeExpirationBucket方法的具體內容:

const UNIT_SIZE = 10;
const MAGIC_NUMBER_OFFSET = 2;

function ceiling(num: number, precision: number): number {
  return (((num / precision) | 0) + 1) * precision;
}

function computeExpirationBucket(
  currentTime,
  expirationInMs,
  bucketSizeMs,
): ExpirationTime {
  return (
    MAGIC_NUMBER_OFFSET +
    ceiling(
      currentTime - MAGIC_NUMBER_OFFSET + expirationInMs / UNIT_SIZE,
      bucketSizeMs / UNIT_SIZE,
    )
  );
}

看完之後,一臉懵。它在搞什麼?別急,我們把公式整理一下:
以低優先級更新爲例,最終的公式是:((((currentTime - 2 + 5000 / 10) / 25) | 0) + 1) * 25

其中只有只有currentTime是變量。

我們可以多試幾個值看看:

((((101 - 2 + 5000 / 10) / 25) | 0) + 1) * 25 // 600
((((102 - 2 + 5000 / 10) / 25) | 0) + 1) * 25 // 625
((((105 - 2 + 5000 / 10) / 25) | 0) + 1) * 25 // 625
((((122 - 2 + 5000 / 10) / 25) | 0) + 1) * 25 // 625
((((126 - 2 + 5000 / 10) / 25) | 0) + 1) * 25 // 625
((((127 - 2 + 5000 / 10) / 25) | 0) + 1) * 25 // 650

簡單來說,最終結果是以25爲單位向上增加的,比如說我們輸入102 - 126之間,最終得到的結果都是625,但是到了127得到的結果就是650了,這就是除以25取整的效果。
即,低優先級更新的expirationTime間隔是25ms,抹平了25ms內計算過期時間的誤差,React讓兩個相近(25ms內)的得到update相同的expirationTime,目的就是讓這兩個update自動合併成一個Update,從而達到批量更新。

注:這裏如果用高優先級更新去嘗試多組數據,你會發現expirationTime間隔是10ms。

任世界紛繁複雜,仍舊保持可愛。
我是小柚子小仙女。文章如有不妥,歡迎指正~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章