php網(wǎng)站開(kāi)發(fā)教程網(wǎng)手機(jī)優(yōu)化大師下載
expirationTime 的計(jì)算方式
- 先看expirationTime相關(guān)的源代碼,這里是異步的計(jì)算方式,它會(huì)有一個(gè)過(guò)期時(shí)間
- 異步任務(wù)優(yōu)先級(jí)比較低,可以被打斷,防止一直被打斷導(dǎo)致不能執(zhí)行,所以React給它設(shè)置了 expirationTime 過(guò)期時(shí)間
- 也就是在這個(gè)時(shí)間之前,都可以打斷,但是如果某個(gè)時(shí)間點(diǎn)發(fā)現(xiàn)任務(wù)已經(jīng)過(guò)期了,還沒(méi)有被執(zhí)行,則強(qiáng)制執(zhí)行該任務(wù)
- 在
ReactDOM.render
當(dāng)中,它計(jì)算 expirationTime 的地方- 在 ReactFiberReconciler.js 中的
updateContainer
函數(shù)中,通過(guò)computeExpirationForFiber
方法來(lái)計(jì)算一個(gè)過(guò)期時(shí)間const current = container.current; // 參數(shù)2 const currentTime = requestCurrentTime(); // 參數(shù)1 我們可以近似理解為: 當(dāng)前時(shí)間到j(luò)s加載完成的時(shí)間的時(shí)間差值即可 const expirationTime = computeExpirationForFiber(currentTime, current);
- 而
requestCurrentTime
這個(gè)函數(shù),來(lái)自于 ReactFiberScheduler.js 中function requestCurrentTime() {// 這里,我把官方注釋移除// 這里表示 已經(jīng)進(jìn)入到 渲染階段 了,在 ReactDOM.render 中這里不會(huì)匹配,會(huì)跳過(guò)// 在一次render中,如果我有一個(gè)新的任務(wù)進(jìn)來(lái)了,要計(jì)算 expirationTime 發(fā)現(xiàn)現(xiàn)在處于渲染階段,這時(shí)直接返回上次 render 開(kāi)始的時(shí)間,再去計(jì)算 expirationTime// 好處是 前后兩次計(jì)算出來(lái)的 expirationTime 是一樣的,讓這個(gè)任務(wù)提前進(jìn)行調(diào)度if (isRendering) {// We're already rendering. Return the most recently read time.return currentSchedulerTime;}// Check if there's pending work.findHighestPriorityRoot();// 剛初始化的時(shí)候,這個(gè)條件是成立的if (nextFlushedExpirationTime === NoWork ||nextFlushedExpirationTime === Never) {// If there's no pending work, or if the pending work is offscreen, we can// read the current time without risk of tearing.recomputeCurrentRendererTime();currentSchedulerTime = currentRendererTime; // 兩個(gè)常量劃等號(hào)return currentSchedulerTime;}// 這里,我把官方注釋移除return currentSchedulerTime; }
findHighestPriorityRoot
方法涉及到從調(diào)度隊(duì)列中找到權(quán)限最高的 Root- 這個(gè)源碼比較多,不做擴(kuò)展
recomputeCurrentRendererTime
每一次做計(jì)算都是從當(dāng)前到j(luò)s加載完成后的時(shí)間間隔,再經(jīng)過(guò)一些計(jì)算得到的值,function recomputeCurrentRendererTime() {const currentTimeMs = now() - originalStartTimeMs; // 當(dāng)前時(shí)間 - react buddle加載完成之后初始的時(shí)間,也就是從js加載完成到現(xiàn)在的時(shí)間間隔currentRendererTime = msToExpirationTime(currentTimeMs); // 計(jì)算出 currentRendererTime }// ReactFiberExpirationTime.js 這個(gè)函數(shù)得到一個(gè)時(shí)間戳 export function msToExpirationTime(ms: number): ExpirationTime {// Always add an offset so that we don't clash with the magic number for NoWork.return ((ms / UNIT_SIZE) | 0) + MAGIC_NUMBER_OFFSET; // UNIT_SIZE 是固定的 10, | 0 是取整的意思, MAGIC_NUMBER_OFFSET 是固定的 2 }
- 關(guān)于
expirationTime
的計(jì)算函數(shù)computeExpirationForFiber
有一個(gè)計(jì)算公式 - 在這個(gè)計(jì)算時(shí)間中,不需要考慮調(diào)度,只考慮計(jì)算公式,在 ReactFiberExpirationTime.js 中
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,)); }// 低權(quán)限計(jì)算 export function computeAsyncExpiration(currentTime: ExpirationTime, ): ExpirationTime {return computeExpirationBucket(currentTime,LOW_PRIORITY_EXPIRATION, // 5000LOW_PRIORITY_BATCH_SIZE, // 250); }// 高權(quán)限計(jì)算 export function computeInteractiveExpiration(currentTime: ExpirationTime) {return computeExpirationBucket(currentTime,HIGH_PRIORITY_EXPIRATION, // 500/150 前 DEV, 后 PRODHIGH_PRIORITY_BATCH_SIZE, // 100); }
- 上面兩個(gè) export 方法,都是調(diào)用
computeExpirationBucket
方法來(lái)計(jì)算的 - 兩個(gè)方法的區(qū)別在于 后面第2和第3個(gè)參數(shù)是不一樣的
- 最終得到的公式是:
((((currentTime - 2 + 5000 / 10) / 25) | 0 ) + 1) * 25
- 其中 25 = 250 / 10,上述公式中的25,也可能是 10 (100 / 10)
- 最終計(jì)算出來(lái)的
expirationTime
是以bucketSize / UNIT_SIZE
這個(gè)單元向上疊加的 - 兩個(gè) 不同的
expirationTime
的差距是 單元值的 倍數(shù) - 對(duì)于
LOW_PRIORITY_BATCH_SIZE
是 以 25 為單元向上加的, 若前后差距在25以內(nèi),計(jì)算出來(lái)的差距都是一樣的 - 對(duì)于
HIGH_PRIORITY_BATCH_SIZE
是 以 10 為單元向上加的,同上 - React 這么設(shè)定的原因: 在計(jì)算
expirationTime
時(shí) - 在一個(gè)操作內(nèi)多次調(diào)用
setState
, 即便前后調(diào)用差距很小,但從毫秒級(jí)別看,還是有差距的 - 如果沒(méi)有提供任何一個(gè)調(diào)整空間,即便上個(gè)
setState
和 下一個(gè)setState
之間差距特別小,算出來(lái)的expirationTime
結(jié)果不一樣 - 這就意味著,兩次的任務(wù)優(yōu)先級(jí)不一樣, 會(huì)導(dǎo)致 React 整體更新執(zhí)行多次,而導(dǎo)致整個(gè)應(yīng)用的性能下降,這就是 設(shè)置 單元值的 用處
- 在一個(gè)差距很小的時(shí)間間隔內(nèi),算出來(lái)的
expirationTime
結(jié)果一樣,則它們優(yōu)先級(jí)也是一樣的,而不需要進(jìn)行區(qū)分 - 這個(gè)非常重要
- 最終計(jì)算出來(lái)的
| 0
表示 去余取整- 其中 (currentTime - 2 + 5000 / 10) 是一個(gè)不會(huì)變化的值,設(shè)為 x
- 后面就是 (((x / 25) | 0) + 1) * 25
- 這個(gè)公式的意義在于,新老值之間的差距在25以內(nèi),則結(jié)果相等
- 其中 25 = 250 / 10,上述公式中的25,也可能是 10 (100 / 10)
- 上面兩個(gè) export 方法,都是調(diào)用
- 在 ReactFiberReconciler.js 中的
- 至于
expirationTime
的作用還要結(jié)合后期更新的流程來(lái)看 expirationTime
是一個(gè)和業(yè)務(wù)無(wú)關(guān)的比較純粹的計(jì)算過(guò)程問(wèn)題,沒(méi)有任何副作用