react源碼淺析(六):到期時間的計算規則

NodeNote,持續更新中react相關庫源碼淺析react ts3 項目react

[TOC]git

到期時間的計算方法

到期時間算法源碼位置:github

react\packages\react-reconciler\src\ReactFiberExpirationTime.js
複製代碼

摘要: react主要把到期時間分爲兩種:異步任務到期時間與交互動做的到期時間。在這以前須要瞭解一下一些重要的函數,react的到期時間與系統的時間ms不是1:1的關係,低優先級異步任務的兩個時間間隔相差不到250ms(至關於25個單位的 到期時間)的任務會被設置爲同一個到期時間,交互 異步任務間隔爲100ms(10個單位到期時間),所以減小了一些沒必要要的組件渲染,而且保證交互能夠及時的響應。算法

precision 單位的步進的到期時間
//向上取整,間隔在precision內的兩個num最終獲得的相同的值
function ceiling(num: number, precision: number): number {
  return (((num / precision) | 0) + 1) * precision;
}

//根據到期時間與單位爲ms的時間之間的轉換關係,定製ceiling來獲得到期時間
const UNIT_SIZE = 10;// 過時時間單元(ms)
const MAGIC_NUMBER_OFFSET = MAX_SIGNED_31_BIT_INT - 1;// 到期時間偏移量
function computeExpirationBucket(
  currentTime,
  expirationInMs,
  bucketSizeMs,
): ExpirationTime {
  return (
    MAGIC_NUMBER_OFFSET -
    ceiling(
      MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE,
      bucketSizeMs / UNIT_SIZE,
    )
  );
}
複製代碼

ceiling的做用:向上取整,間隔在precision內的兩個num最終獲得的相同的值。 若是precision爲25,則50和66轉換後的到期時間都是75bash

computeExpirationBucket的做用:第一參數是須要轉換的當前時間(單位:單位到期時間 = UNIT_SIZE ms),第二個參數是不一樣優先級的異步任務對應的偏移時間(單位:ms),第三個參數是步進時間(單位:ms)。該函數經過定製ceiling獲得特定單位(10ms一個單位)的到期時間,這個時間對應不一樣優先級的異步任務到期時間。好比:若是是低優先級的異步任務,則第二個參數傳入LOW_PRIORITY_EXPIRATION = 5000異步

因爲到期時間越大,優先級越高,所以第二個參數的巧妙之處是,避免在一個當前
時間左右的不一樣優先級任務的到期時間相差無幾,失去了到期時間的意義。
複製代碼

低優先級異步任務到期時間: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,
    LOW_PRIORITY_BATCH_SIZE,
  );
}
複製代碼

上面currentTime5058轉換後的到期時間都是1073742275:性能

50: ((1073741822-50+500)/25|0+1)*25 = 1073742275
58: ((1073741822-58+500)/25|0+1)*25 = 1073742275
複製代碼

交互動做的到期時間:computeInteractiveExpirationui

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,
    HIGH_PRIORITY_BATCH_SIZE,
  );
}
複製代碼

上面currentTime5058轉換後的到期時間相等,交互任務在開發環境獲得的到期時間大於生產環境:spa

開發環境:
50: ((1073741822-50+50)/10|0+1)*10 = 1073741830
58: ((1073741822-58+50)/10|0+1)*10 = 1073741830

生產環境:
50: ((1073741822-50+15)/10|0+1)*10 = 1073741790
58: ((1073741822-58+15)/10|0+1)*10 = 1073741790
複製代碼

獲取當前時間currentTime:requestCurrentTime

function requestCurrentTime() {
      if (isRendering) {
        return currentSchedulerTime;
      }
      findHighestPriorityRoot();
      if (
        nextFlushedExpirationTime === NoWork ||
        nextFlushedExpirationTime === Never
      ) {
        recomputeCurrentRendererTime();
        currentSchedulerTime = currentRendererTime;
        return currentSchedulerTime;
      }
      return currentSchedulerTime;
}
複製代碼

在 React 中咱們計算expirationTime要基於當前得時鐘時間,通常來講咱們只須要獲取Date.now或者performance.now就能夠了,可是每次獲取一下呢比較消耗性能,因此 React 設置了currentRendererTime來記錄這個值,用於一些不須要從新計算得場景。

\react\packages\react-reconciler\src\ReactFiberScheduler.js中能夠發現以下代碼是同時出現的,先獲取到當前時間賦值給currentRendererTime,而後currentRendererTime賦值給currentSchedulerTime

recomputeCurrentRendererTime();
    currentSchedulerTime = currentRendererTime;
複製代碼
在上述requestCurrentTime函數中,首先看第一個判斷:
if (isRendering) {
        return currentSchedulerTime;
      }
複製代碼

isRendering會在performWorkOnRoot的開始設置爲true,在結束設置爲false,都是同步的。performWorkOnRoot的先進入渲染階段而後進入提交階段,react全部的生命週期鉤子都是在此執行的。

在一個事件回調函數中調用屢次setState的時候,isRendering老是false,若是是在生命週期鉤子函數componentDidMount中調用setState的時候,isRenderingtrue,由於該鉤子觸發的時機就是在performWorkOnRoot中。

再看findHighestPriorityRoot();

findHighestPriorityRoot會找到root雙向鏈表(React.render會建立一個root並添加到這個雙向鏈表中)中有任務須要執行而且到期時間最大即優先級最高的任務,而後將這個須要更新的root以及最大到期時間賦值給nextFlushedRoot以及nextFlushedExpirationTime。當沒有任務的時候nextFlushedExpirationTimeNoWork

接着看第二個判斷
if (
        nextFlushedExpirationTime === NoWork ||
        nextFlushedExpirationTime === Never
      ) {
        recomputeCurrentRendererTime();
        currentSchedulerTime = currentRendererTime;
        return currentSchedulerTime;
      }
複製代碼

若是沒有任務須要執行,那麼從新計算當前時間,並返回,在事件處理函數中第一個setState會從新計算當前時間,可是第二個setState的時候,因爲已經有更新任務在隊列中了,因此這裏直接跳過判斷,最後返回上一次setState時的記錄的當前時間。

注意:這裏調用的recomputeCurrentRendererTime是經過調用performance.now()或者Date.now()獲取的時間。

最後
return currentSchedulerTime;
複製代碼

返回currentSchedulerTime

計算到期時間:computeExpirationForFiber

經過一些標誌來判斷當前fiber發生的更新是處於什麼階段,來計算相應的到期時間。

function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
  let expirationTime;
  if (expirationContext !== NoWork) {
    expirationTime = expirationContext;
  } else if (isWorking) {
    //在renderRoot與commitRoot階段isWorking = true,結束以後都會是false
    //  下面就判斷是哪一個階段
    if (isCommitting) {
      expirationTime = Sync;
    } else {
      expirationTime = nextRenderExpirationTime;
    }
  } else {
    if (fiber.mode & ConcurrentMode) {
      if (isBatchingInteractiveUpdates) {
        expirationTime = computeInteractiveExpiration(currentTime);
      } else {
        expirationTime = computeAsyncExpiration(currentTime);
      }
      if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
        expirationTime -= 1;
      }
    } else {
      expirationTime = Sync;
    }
  }
  if (isBatchingInteractiveUpdates) {
    if (
      lowestPriorityPendingInteractiveExpirationTime === NoWork ||
      expirationTime < lowestPriorityPendingInteractiveExpirationTime
    ) {
      lowestPriorityPendingInteractiveExpirationTime = expirationTime;
    }
  }
  return expirationTime;
}
複製代碼

主要判斷的流程以下:

注意:同步Sync優先級最高

若是context有更新任務須要執行
    expirationTime設置爲context上的到期時間
若是處於renderRoot渲染階段或者commitRoot提交階段
    若是處於commitRoot
        expirationTime設置爲同步Sync
    不然(處於renderRoot)
        expirationTime設置爲當前的到期時間nextRenderExpirationTime
不然
    若是不是Concurrent模式
        若是正在批處理交互式更新
            利用computeInteractiveExpiration計算expirationTime
        不然
            利用computeAsyncExpiration計算expirationTime
        若是有下一root樹須要更新,而且到期時間與該樹到期時間相等
            expirationTime減一,表示讓下一個root先更新
    不然
        expirationTime設置爲同步Sync

若是正在批處理交互式更新
    若是最低優先級的交互式更新優先級大於到期時間expirationTime或者沒有交互式更新任務
        將最低優先級的交互式更新任務到期時間設置爲到期時間expirationTime

最後返回expirationTime
複製代碼

總結

同一個事件,同一個生命週期中的setState具有相同的到期時間,所以也存在了多個setState合併的結果。

相關文章
相關標籤/搜索