React中useEffect 與 useLayoutEffect的區別
目錄
- 前置知識
- useEffect
- commitBeforeMutationEffects
- commitMutationEffects
- commitLayoutEffects
- 後續階段
- useLayoutEffect
- 結論
前置知識
我們可以將 React 的工作流程劃分為幾大塊:
- render 階段:主要生成 Fiber節點 並構建出完整的 Fiber樹
- commit 階段:www.cppcns.com在上一個render 階段中會在 rootFiber 上生成一條副作用連結串列,應用的DOM操作就會在本階段執行
commit階段的工作主要分為三部分,對應到原始碼中的函式名是:
- commitBeforeMutationEffects階段:主要處理執行DOM操作前的一些相關操作
- commitMutationEffects階段:執行DOM操作
- commitLayoutEffects階段:主要處理執行DOM操作後的一些相關操作
useEffect 和 useLayoutEffect 的區別主要就在體現在這三個階段的處理上。結論是:useEffect 會非同步地去執行它的響應函式和上一次的銷燬函式,而useLayoutEffect 會同步地執行它的響應函式和上一次的銷燬函式,即會阻塞住 DOM渲染。
useEffect
commitBeforeMutationEffects
在這個階段中 useEffect 著重會經歷一句話如下:
function commitBeforeMutationEffects() { while (nextEffect$1 !== null) { // 一系列的賦值操作省略,這裡的flags應取自對應FunctionComponent的effect的flags,具體實現請看原始碼 var flags = effect.flags; // 處理生命週期 if ((flags & Snapshot) !== NoFlags) { setCurrentFiber(nextEffect$1); commitBeforeMutationLifeCycles(current,nextEffect$1); resetCurrentFiber(); } // 這個if判斷只有 useEffect 為 true,useLayoutEffect 為false if ((flags & Passive) !== NoFlags) { // If there are passive effects,schedule a callback to flush at // the earliest opportunity. if (!rootDoesHavePass客棧iveEffects) { rootDoesHavePassiveEffects = true; // 這裡就是 useEffect 非同步的原因,DOM操作後React會排程 flushPassiveEffects scheduleCallback(NormalPriority,function () { flushPassiveEffects(); return null; }); } } nextEffect$1 = nextEffect$1.nextEffect; } }
commitMutationEffects
在這個階段www.cppcns.com中,React 會進行一系列的DOM節點更新 ,然後會執行一個方法: commitHookEffectListUnmount(HookLayout | HookHasEffect,finishedWork);
那麼一個擁有 useEffect 的 Functional Component 在這個階段是不符合 unmount 的判斷邏輯的,所以在這個地方不會做 unmount 操作。
commitLayoutEffects
在這個階段中,依然有一個很重要的方法存在:commitHookEffectListMount(HookLayout | HookHasEffect,finishedWork);
這個if判斷和上一階段的if判斷是一樣的,useEffec 在這個判斷中不會做任何操作。
後續階段
在完成了 commitLayoutEffects 後,還有一個操作:
if (rootDoesHavePassiveEffects) { // This commit has passive effects. Stash a reference to them. But don't // schedule a callback until after flushing layout work. rootDoesHavePassiveEffects = false; rootWithPendingPassiveEffects = root; pendingPassiveEffectsLanes = lanes; pendingPassiveEffectsRenderPriority = renderPriorityLevel; }
即把 rootWithPendingPassiveEffects 置為 root ,這麼做的原因和第一階段 commitBeforeMutationEffects 中 useEffect 註冊的下一次 flushPassiveEffects 非同步排程有關,我們看以下 flushPassiveEffects 的實現:
function flushPassiveEffectsImpl() {
if (rootWithPendingPassiveEffects === null) {
www.cppcns.com return false;
}
// 省略一系列的效能追蹤等操作
commitPassiveUnmountEffects(root.current);
commitPassiveMountEffects(root,root.current);
}
從上述程式碼段可以看見,useEffect 在第一階段註冊的排程回撥會在頁面更新後進行 unmount 和 mount 操作。值得一提的是,這個回撥中effect的註冊時機就是在 commitLayoutEffects 階段。
useLayoutEffect
其實根據我們對 useEffect 的解析來看,就是在 commitMutationEffects 和 commitLayoutEffects 階段中各自的 if 判斷中,useLayoutEffect 是通過if判斷的,所以在 commitMutationEffects 階段中,同步執行了useLayoutEffect 的上一次銷燬函式,在 commitLayoutEffects 階段中,同步執行了 useLayoutEffect 本次的執行函式,並註冊上銷燬函式。
結論
至此,我們粗略地查看了 commit 階段的程式碼,分析了以下為什麼 useEffect 是非同步執行,而 useLayoutEffect 是同步執行,具體的程式碼我沒有太過在文章中貼出來,因為這些都是可變的,真正的流程性的概覽和 React 團隊設計這一套機制的心智模型需要我們自己在不斷除錯程式碼和理解中慢慢去熟悉。
後續自己感興趣的是 hooks 的實現,其中比較關鍵的 useReducer 會著重看一下原始碼,看看能不能寫個簡易版本的放到支付寶小程式中去實現一個 自定義的支付寶hooks 用於日常生產力開發。
到此這篇關於React中useEffect 與 useLayoutEffect的區別的文章就介紹到這了,更多相關React useEffect useLayoutEffect內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!