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