背景
接上一章 React 源碼探源 4 useState莫瞬,來研究一下 useEffect 與 useLayoutEffect 相關(guān)的實(shí)現(xiàn)細(xì)節(jié)咕缎。
相關(guān)定義
先來看一下 React 相關(guān)的官方文檔
-
useEffect,
- 作用:此 hook 主要是用來在組件渲染完成以后執(zhí)行一些稱為 effect 的操作冕象,例如發(fā)送 ajax 請(qǐng)求,打點(diǎn)汁蝶,當(dāng)一些 state 發(fā)生變化以后渐扮,再更新其它的 state 等。
- 執(zhí)行時(shí)機(jī):
useEffect
實(shí)在下次渲染之前執(zhí)行掖棉,執(zhí)行時(shí)瀏覽器已經(jīng)對(duì)上次狀態(tài)更新渲染完成墓律。 - 返回值:
useEffect
可以返回一個(gè)回調(diào)函數(shù),當(dāng)組件unMount
時(shí)幔亥,會(huì)被調(diào)用耻讽。
-
useLayoutEffect
- 作用:如文檔所示,絕大部分情況下都推薦使用
useEffect
帕棉,只有使用useEffect
的結(jié)果有些怪異時(shí)才會(huì)使用這個(gè) hook针肥。 筆者根據(jù)實(shí)驗(yàn)發(fā)現(xiàn),只有更改 DOM 時(shí)導(dǎo)致了一些抖動(dòng)的行為時(shí)使用useLayoutEffect
時(shí)才會(huì)派上用場(chǎng)香伴。 - 執(zhí)行時(shí)機(jī):
useLayoutEffect
執(zhí)行時(shí)慰枕,瀏覽器還未對(duì) DOM 進(jìn)行渲染〖锤伲可以獲取新的 DOM 進(jìn)行操作具帮。執(zhí)行的時(shí)機(jī)較useEffect
更早。 - 返回值:
useLayoutEffect
也可以返回一個(gè)回調(diào)函數(shù),也會(huì)在unMount
時(shí)被調(diào)用蜂厅。調(diào)用的時(shí)機(jī)也會(huì)較useEffect
的回調(diào)更早匪凡。
- 作用:如文檔所示,絕大部分情況下都推薦使用
示例代碼
本次實(shí)例使用的詳細(xì)代碼如下
function Dev() {
const [count, setCount] = React.useState(0);
React.useEffect(function effectCb() {
console.log('in effect');
if (count === 1) {
setCount(10);
}
return function effectUnMount() {
console.log('effect unmount');
};
}, [count]);
React.useLayoutEffect(function layoutEffectCb() {
console.log('in layout effect', count);
return function layoutEffectUnMount() {
console.log('layout effect unmount');
};
}, [count]);
return (<div id="div">
<button id="btn" onClick={() => {
setCount(function add(c) {return c + 1;});
}}>click me</button>
<div>the new text is <span>{count}</span></div>
</div>);
}
詳細(xì)流程
useEffect 和 useLayoutEffect 的詳細(xì)流程
render
- 在執(zhí)行
useEffect
,useLayoutEffect
時(shí)會(huì)將對(duì)應(yīng)的回調(diào)存儲(chǔ)起來,詳細(xì)結(jié)構(gòu)參加下部分 - 更新時(shí)葛峻,會(huì)檢查對(duì)應(yīng)的
dependency
是不是有變化再?zèng)Q定是否將effect
加進(jìn)來锹雏。
commit
-
useEffect
的列表會(huì)先被檢查,如果有更新术奖,會(huì)使用MessageChannel.postMessage
計(jì)劃在下次 eventLoop 執(zhí)行礁遵。從代碼注釋中看到,這樣的執(zhí)行方式比setTimeout
要好采记,因?yàn)?setTimeout
至少有 4ms 的延遲佣耐。 -
useLayoutEffect
會(huì)在commitMutationEffects
,也就是內(nèi)存中的 DOM 更新以后馬上執(zhí)行唧龄,執(zhí)行的時(shí)機(jī)比useEffect
更早兼砖。 - effect 執(zhí)行時(shí),會(huì)檢查 fiber 的
updateQueue
中是否含有對(duì)應(yīng)類型的 effect既棺,并將它順序執(zhí)行讽挟。 - 執(zhí)行過程類似與
useState
的調(diào)用類似。- 通過
subtreeFlags
來檢查子節(jié)點(diǎn)是否有 effect 需要執(zhí)行丸冕。 - 通過
flags
檢查當(dāng)前節(jié)點(diǎn)有 effect 需要執(zhí)行耽梅。
- 通過
effect 數(shù)據(jù)結(jié)構(gòu)
可以看到以下的信息:
-
useLayoutEffect
,useEffect
生成的 hook 會(huì)跟useState
生成的 hook 一起存儲(chǔ)在 fiber 的memorizedState
下面的鏈表里面胖烛。 - fiber 的
updateQueue
使用lastEffect
存儲(chǔ)著所有的 effect 生成的循環(huán)鏈表眼姐。 - hook 中的
memorizedState
和lastEffect
指向相同的地方,存儲(chǔ)著 effect 的相關(guān)信息-
create
: effect 的回調(diào)函數(shù) -
tag
: 存儲(chǔ)著effect
的類型Passive = 4
標(biāo)記著useEffect
LayoutStatic = 2
標(biāo)記著useLayoutEffect
-
destroy
: effect 返回的回調(diào)函數(shù) -
next
: 下一個(gè)可能的 effect
-
unMount 過程
在組件卸載時(shí)佩番,執(zhí)行的順序和機(jī)制與加載和更新時(shí)一致众旗,只是在檢查到 fiber 被刪除時(shí)進(jìn)行操作。