React源碼解析之ExpirationTime

一、ExpirationTime的作用
React中,爲防止某個update因爲優先級的原因一直被打斷而未能執行。React會設置一個ExpirationTime,當時間到了ExpirationTime的時候,如果某個update還未執行的話,React將會強制執行該update,這就是ExpirationTime的作用。

二、位置
React源碼解析之ReactDOM.render()中,已經講解了updateContainer()

export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): ExpirationTime {
   ...
   //計算過期時間,這是React優先級更新非常重要的點
  const expirationTime = computeExpirationForFiber(
    currentTime,
    current,
    suspenseConfig,
  );
   ...
}

computeExpirationForFiber

//爲fiber對象計算expirationTime
export function computeExpirationForFiber(
  currentTime: ExpirationTime,
  fiber: Fiber,
  suspenseConfig: null | SuspenseConfig,
): ExpirationTime {
  ...
  // Compute an expiration time based on the Scheduler priority.
    switch (priorityLevel) {
      case ImmediatePriority:
        expirationTime = Sync;
        break;
      case UserBlockingPriority:
        // TODO: Rename this to computeUserBlockingExpiration
        //一個是計算交互事件(如點擊)的過期時間
        expirationTime = computeInteractiveExpiration(currentTime);
        break;
      case NormalPriority:
      case LowPriority: // TODO: Handle LowPriority
        // TODO: Rename this to... something better.
        //一個是計算異步更新的過期時間
        expirationTime = computeAsyncExpiration(currentTime);
        break;
      case IdlePriority:
        expirationTime = Never;
        break;
      default:
        invariant(false, 'Expected a valid priority level');
    }
  ...
}

我們可以看到有兩個計算expirationTime的方法,分別爲computeInteractiveExpiration()computeAsyncExpiration()

先看下computeAsyncExpiration()

三、computeAsyncExpiration()
作用:
返回低優先級(普通異步更新)的expirationTime(過期時間)

源碼:

//低權限的過期時間
export const LOW_PRIORITY_EXPIRATION = 5000;
export const LOW_PRIORITY_BATCH_SIZE = 250;
//普通的異步的expirationTime
export function computeAsyncExpiration(
  currentTime: ExpirationTime,
): ExpirationTime {
  return computeExpirationBucket(
    currentTime,
    //5000
    LOW_PRIORITY_EXPIRATION,
    //250
    LOW_PRIORITY_BATCH_SIZE,
  );
}

解析:
currentTime先按下不表,LOW_PRIORITY_EXPIRATION5000LOW_PRIORITY_BATCH_SIZE 250,注意它的名字LOW_PRIORITY_BATCH_SIZE ,下面會提到

四、computeExpirationBucket()
作用:
計算過期時間

源碼:

//1073741823
export const Sync = MAX_SIGNED_31_BIT_INT;
//1073741822
export const Batched = Sync - 1;

const UNIT_SIZE = 10;
//1073741821
const MAGIC_NUMBER_OFFSET = Batched - 1;

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

//計算過期時間
function computeExpirationBucket(
  currentTime,
  expirationInMs,
  bucketSizeMs,
): ExpirationTime {
  return (
    //1073741821
    MAGIC_NUMBER_OFFSET -
    ceiling(
      //   1073741821-currentTime+(high 150 或者 low 5000  /10)  ,
      MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE,
      //(high 100 或者 low 250  /10 )
      bucketSizeMs / UNIT_SIZE,
    )
  );
}

解析:
(1)MAX_SIGNED_31_BIT_INT

// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
// Math.pow(2, 30) - 1
// 0b111111111111111111111111111111
//整型最大數值,是V8中針對32位系統所設置的最大值
export default 1073741823;

(2)| 0的意思是取整

console.log(16/3 |0) //5

(3)根據computeExpirationBucket()裏面的公式,計算下異步更新的過期時間:

   //low 情況
  1073741821-ceiling(1073741821-currentTime+500,25)

  1073741821-((((1073741821-currentTime+500) / 25) | 0) + 1) * 25

  1073741821-((1073741821/25-currentTime/25+20 | 0) + 1) * 25

  1073741821-((1073741821/25-currentTime/25+20*25/25 | 0) + 1) * 25

  1073741821-((1073741821-currentTime+500)/25 | 0)*25 - 25

  1073741796-((1073741821-currentTime+500)/25 | 0)*25

  1073741796-((1073742321-currentTime)/25 | 0)*25
  //======我們直接取最後四位來探索規律===================
  1796-((2321-currentTime)/25 | 0)*25
  //假設 currentTime 是 2000
  1796-(2321- 2000 /25 | 0)*25 //1796-300
  //currentTime是2010
  1796-(311/25 | 0)*25 //1796-300
  //currentTime是2024
  1796-(311/25 | 0)*25 //1796-275
  //currentTime是2025
  1796-(311/25 | 0)*25 //1796-275

可以看到,低優先的過期時間間隔25ms
同理,高優先級的過期時間間隔10ms

  //high 情況
  1073741821-ceiling(1073741821-currentTime+15,10)

**也就是說,React低優先級updateexpirationTime間隔是25ms
React讓兩個相近(25ms內)的update得到相同的expirationTime,目的就是讓這兩個update自動合併成一個Update,從而達到批量更新的目的,就像LOW_PRIORITY_BATCH_SIZE的名字一樣,自動合併批量更新。**

想象一下,開發者不停地使用setState()更新ReactApp,如果不把相近的update合併的話,會嚴重影響性能,就像提到的doubleBuffer一樣,React爲提高性能,考慮得非常全面!

Github:
https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberExpirationTime.js


(完)

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