React深入分析useEffect源碼_第1頁
React深入分析useEffect源碼_第2頁
React深入分析useEffect源碼_第3頁
React深入分析useEffect源碼_第4頁
React深入分析useEffect源碼_第5頁
已閱讀5頁,還剩3頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

第React深入分析useEffect源碼目錄熱身準(zhǔn)備初始化mount更新updateupdateEffect執(zhí)行副作用總結(jié)

熱身準(zhǔn)備

這里不再講useLayoutEffect,它和useEffect的代碼是一樣的,區(qū)別主要是:

執(zhí)行時(shí)機(jī)不同;useEffect是異步,useLayoutEffect是同步,會阻塞渲染;

初始化mount

mountEffect

在所有hook初始化時(shí)都會通過下面這行代碼實(shí)現(xiàn)hook結(jié)構(gòu)的初始化和存儲,這里不再講mountWorkInProgressHook方法

varhook=mountWorkInProgressHook();

在mountEffect方法中,只有這幾行代碼。先來解讀下幾個(gè)參數(shù):

fiberFlags:有副作用的更新標(biāo)記,用來標(biāo)記hook所在的fiber;hookFlags:副作用標(biāo)記;create:使用者傳入的回調(diào)函數(shù);deps:使用者傳入的數(shù)組依賴;

functionmountEffectImpl(fiberFlags,hookFlags,create,deps){

//hook初始化

varhook=mountWorkInProgressHook();

//判斷是否有傳入deps,如果有會作為下次更新的deps

varnextDeps=deps===undefinednull:deps;

//給hook所在的fiber打上有副作用的更新的標(biāo)記

currentlyRenderingFiber$1.flags|=fiberFlags;

//將副作用操作存放到fiber.memoizedState.hook.memoizedState中

hook.memoizedState=pushEffect(HasEffect|hookFlags,create,undefined,nextDeps);

上面代碼中都有注釋,接下來我們看看React是如何存放副作用更新操作的,主要就是pushEffect方法

functionpushEffect(tag,create,destroy,deps){

//初始化副作用結(jié)構(gòu),

vareffect={

tag:tag,

create:create,//回調(diào)函數(shù)

destroy:destroy,//回調(diào)函數(shù)里的return(mount時(shí)是undefined)

deps:deps,//依賴數(shù)組

//閉環(huán)鏈表

next:null

//下面的一大段代碼看著復(fù)雜,但是有沒有很熟悉的感覺?

varcomponentUpdateQueue=currentlyRenderingFiber$1.updateQueue;

if(componentUpdateQueue===null){

componentUpdateQueue=createFunctionComponentUpdateQueue();

currentlyRenderingFiber$1.updateQueue=componentUpdateQueue;

//effect.next=effect形成環(huán)形鏈表

componentUpdateQueue.lastEffect=effect.next=effect;

}else{

varlastEffect=componentUpdateQueue.lastEffect;

if(lastEffect===null){

componentUpdateQueue.lastEffect=effect.next=effect;

}else{

varfirstEffect=lastEffect.next;

lastEffect.next=effect;

effect.next=firstEffect;

componentUpdateQueue.lastEffect=effect;

returneffect;

}

上面這段代碼除了初始化副作用的結(jié)構(gòu)代碼外,都是我們前面講過的操作閉環(huán)鏈表,向鏈表末尾添加新的effect,該effect.next指向fisrtEffect,并且鏈表當(dāng)前的指針指向最新添加的effect。

useEffect的初始化就這么簡單,簡單總結(jié)一下:給hook所在的fiber打上副作用更新標(biāo)記,并且fiber.memoizedState.hook.memoizedState和fiber.updateQueue存儲了相關(guān)的副作用,這些副作用通過閉環(huán)鏈表的結(jié)構(gòu)存儲。

相關(guān)參考視頻講解:傳送門

更新update

updateEffect

updateWorkInProgressHook在上篇文章也已講過,不再詳述,主要功能就是創(chuàng)建一個(gè)帶有回調(diào)函數(shù)的newHook去覆蓋之前的hook。

functionupdateEffectImpl(fiberFlags,hookFlags,create,deps){

varhook=updateWorkInProgressHook();

varnextDeps=deps===undefinednull:deps;

vardestroy=undefined;

if(currentHook!==null){

varprevEffect=currentHook.memoizedState;

destroy=prevEffect.destroy;

if(nextDeps!==null){

varprevDeps=prevEffect.deps;

//比較兩次依賴數(shù)組中的值是否有變化

if(areHookInputsEqual(nextDeps,prevDeps)){

//和之前初始化時(shí)一樣

pushEffect(hookFlags,create,destroy,nextDeps);

return;

//和之前初始化時(shí)一樣

currentlyRenderingFiber$1.flags|=fiberFlags;

hook.memoizedState=pushEffect(HasEffect|hookFlags,create,destroy,nextDeps);

}

相信眼眼尖的看官已經(jīng)注意到上面代碼中有兩個(gè)pushEffect,一個(gè)沒有賦值給hook.memoizedState,一個(gè)賦值了,這兩者有什么區(qū)別呢?

先保留著這個(gè)疑問,先來了解下下面這行代碼都做了些什么,因?yàn)樗炀土藘蓚€(gè)pushEffect。

if(areHookInputsEqual(nextDeps,prevDeps)){...}

functionareHookInputsEqual(nextDeps,prevDeps){

//沒有傳deps的情況返回false

if(prevDeps===null){

returnfalse;

//deps不是[],且其中的值有變動才會返回false

for(vari=0;iprevDeps.lengthinextDeps.length;i++){

if(objectIs(nextDeps[i],prevDeps[i])){

continue;

returnfalse;

//deps=[],或者deps里面的值沒有變化會返回true

returntrue;

}

它會判斷兩次依賴數(shù)組中的值是否有變化以及deps是否是空數(shù)組來決定返回true和false,返回true表明這次不需要調(diào)用回調(diào)函數(shù)。

現(xiàn)在我們明白了兩次pushEffect的異同,if內(nèi)部的pushEffect是不需要調(diào)用的回調(diào)函數(shù),外面的pushEffect是需要調(diào)用的。再來仔細(xì)看下這兩行代碼:

//if內(nèi)部的,第一個(gè)參數(shù)是hookFlags=4

pushEffect(hookFlags,create,destroy,nextDeps);

//if外部的,第一個(gè)參數(shù)是HasEffect|hookFlags=5

hook.memoizedState=pushEffect(HasEffect|hookFlags,create,destroy,nextDeps);

這兩行代碼的區(qū)別是傳入的第一個(gè)參數(shù)不同,而第一個(gè)參數(shù)就是effect.tag的值,effect.tag=4不會添加到副作用執(zhí)行隊(duì)列,而effect.tag=5可以。沒有添加到副作用執(zhí)行隊(duì)列的effect就不會執(zhí)行。這樣就巧妙的實(shí)現(xiàn)了useEffect基于deps來判斷是否需要執(zhí)行回調(diào)函數(shù)。

到這里,我們搞明白了,不管useEffect里的deps有沒有變化都會為回調(diào)函數(shù)創(chuàng)建effect并添加到effect鏈表和fiber.updateQueue中,但是React會根據(jù)effect.tag來決定該effect是否要添加到副作用執(zhí)行隊(duì)列中去執(zhí)行。

執(zhí)行副作用

我們現(xiàn)在知道了,useEffect是異步執(zhí)行的。那么這個(gè)回調(diào)函數(shù)副作用會在什么時(shí)候執(zhí)行呢?useEffect回調(diào)函數(shù)會在layout階段之后執(zhí)行?,F(xiàn)在我們來了解下具體調(diào)用執(zhí)行的流程。

我畫了一個(gè)簡單的流程圖,大致描述了下調(diào)用流程。首先在mutation之前階段,基于副作用創(chuàng)建任務(wù)并放到taskQueue中,同時(shí)會執(zhí)行requestHostCallback,這個(gè)方法就涉及到了異步了,它首先考慮使用MessageChannel實(shí)現(xiàn)異步,其次會考慮使用setTimeout實(shí)現(xiàn)。使用MessageChannel時(shí),requestHostCallback會馬上執(zhí)行port.postMessage(null);,這樣就可以在異步的第一時(shí)間執(zhí)行workLoop,workLoop會遍歷taskQueue,執(zhí)行任務(wù),如果是useEffect的effect任務(wù),會調(diào)用flusnPassiveEffects。

Q:可能有人會疑惑為什么優(yōu)先考慮MessageChannel?

A:首先我們要明白React調(diào)度更新的目的是為了時(shí)間分片,意思是每隔一段時(shí)間就把主線程還給瀏覽器,避免長時(shí)間占用主線程導(dǎo)致頁面卡頓。使用MessageChannel和SetTimeout的目的都是為了創(chuàng)建宏任務(wù),因?yàn)楹耆蝿?wù)會在當(dāng)前微任務(wù)都執(zhí)行完后,等到瀏覽器主線程空閑后才會執(zhí)行。不優(yōu)先考慮setTimeout的原因是,setTimeout執(zhí)行時(shí)間不準(zhǔn)確,會造成時(shí)間浪費(fèi),即使是setTimeout(fn,0),感興趣的可以去自己了解下,本文不做贅述了。

在schedulePassiveEffects中,會決定是否執(zhí)行effect鏈表中的effect,判斷的依據(jù)就是每個(gè)effect上的effect.tag:

functionschedulePassiveEffects(finishedWork){

varupdateQueue=finishedWork.updateQueue;

varlastEffect=updateQueue!==nullupdateQueue.lastEffect:null;

if(lastEffect!==null){

varfirstEffect=lastEffect.next;

vareffect=firstEffect;

//遍歷effect鏈表

do{

var_effect=effect,

next=_effect.next,

tag=_effect.tag;

//基于effect.tag決定是否添加到副作用執(zhí)行隊(duì)列

if((tagPassive$1)!==NoFlags$1(tagHasEffect)!==NoFlags$1){

enqueuePendingPassiveHookEffectUnmount(finishedWork,effect);

enqueuePendingPassiveHookEffectMount(finishedWork,effect);

effect=next;

}while(effect!==firstEffect);

}

在flushPassiveEffects中,會先執(zhí)行上次更新動作的銷毀函數(shù),然后再執(zhí)行本次更新動作的回調(diào)函數(shù),并且會把回調(diào)函數(shù)的return作為下次更新動作的銷毀函數(shù)。

functionflushPassiveEffectsImpl(){

//執(zhí)行上次更新動作的銷毀函數(shù)

varunmountEffects=pendingPassiveHookEffectsUnmou

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論