React中useEffect 與 useLayoutEffect的區(qū)別
前置知識
我們可以將 React 的工作流程劃分為幾大塊:
- render 階段:主要生成 Fiber節(jié)點 并構(gòu)建出完整的 Fiber樹
- commit 階段:在上一個render 階段中會在 rootFiber 上生成一條副作用鏈表,應(yīng)用的DOM操作就會在本階段執(zhí)行
commit階段的工作主要分為三部分,對應(yīng)到源碼中的函數(shù)名是:
- commitBeforeMutationEffects階段:主要處理執(zhí)行DOM操作前的一些相關(guān)操作
- commitMutationEffects階段:執(zhí)行DOM操作
- commitLayoutEffects階段:主要處理執(zhí)行DOM操作后的一些相關(guān)操作
useEffect 和 useLayoutEffect 的區(qū)別主要就在體現(xiàn)在這三個階段的處理上。結(jié)論是:useEffect 會異步地去執(zhí)行它的響應(yīng)函數(shù)和上一次的銷毀函數(shù),而useLayoutEffect 會同步地執(zhí)行它的響應(yīng)函數(shù)和上一次的銷毀函數(shù),即會阻塞住 DOM渲染。
useEffect
commitBeforeMutationEffects
在這個階段中 useEffect 著重會經(jīng)歷一句話如下:
function commitBeforeMutationEffects() { while (nextEffect$1 !== null) { // 一系列的賦值操作省略,這里的flags應(yīng)取自對應(yīng)FunctionComponent的effect的flags,具體實現(xiàn)請看源碼 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 (!rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects = true; // 這里就是 useEffect 異步的原因,DOM操作后React會調(diào)度 flushPassiveEffects scheduleCallback(NormalPriority, function () { flushPassiveEffects(); return null; }); } } nextEffect$1 = nextEffect$1.nextEffect; } }
commitMutationEffects
在這個階段中,React 會進行一系列的DOM節(jié)點更新 ,然后會執(zhí)行一個方法: commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork);
那么一個擁有 useEffect 的 Functional Component 在這個階段是不符合 unmount 的判斷邏輯的,所以在這個地方不會做 unmount 操作。
commitLayoutEffects
在這個階段中,依然有一個很重要的方法存在:commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
這個if判斷和上一階段的if判斷是一樣的,useEffec 在這個判斷中不會做任何操作。
后續(xù)階段
在完成了 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 異步調(diào)度有關(guān),我們看以下 flushPassiveEffects 的實現(xiàn):
function flushPassiveEffectsImpl() { if (rootWithPendingPassiveEffects === null) { return false; } // 省略一系列的性能追蹤等操作 commitPassiveUnmountEffects(root.current); commitPassiveMountEffects(root, root.current); }
從上述代碼段可以看見,useEffect 在第一階段注冊的調(diào)度回調(diào)會在頁面更新后進行 unmount 和 mount 操作。值得一提的是,這個回調(diào)中effect的注冊時機就是在 commitLayoutEffects 階段。
useLayoutEffect
其實根據(jù)我們對 useEffect 的解析來看,就是在 commitMutationEffects 和 commitLayoutEffects 階段中各自的 if 判斷中,useLayoutEffect 是通過if判斷的,所以在 commitMutationEffects 階段中,同步執(zhí)行了useLayoutEffect 的上一次銷毀函數(shù),在 commitLayoutEffects 階段中,同步執(zhí)行了 useLayoutEffect 本次的執(zhí)行函數(shù),并注冊上銷毀函數(shù)。
結(jié)論
至此,我們粗略地查看了 commit 階段的代碼,分析了以下為什么 useEffect 是異步執(zhí)行,而 useLayoutEffect 是同步執(zhí)行,具體的代碼我沒有太過在文章中貼出來,因為這些都是可變的,真正的流程性的概覽和 React 團隊設(shè)計這一套機制的心智模型需要我們自己在不斷調(diào)試代碼和理解中慢慢去熟悉。
后續(xù)自己感興趣的是 hooks 的實現(xiàn),其中比較關(guān)鍵的 useReducer 會著重看一下源碼,看看能不能寫個簡易版本的放到支付寶小程序中去實現(xiàn)一個 自定義的支付寶hooks 用于日常生產(chǎn)力開發(fā)。
到此這篇關(guān)于React中useEffect 與 useLayoutEffect的區(qū)別的文章就介紹到這了,更多相關(guān)React useEffect useLayoutEffect內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React+Antd+Redux實現(xiàn)待辦事件的方法
這篇文章主要介紹了React+Antd+Redux實現(xiàn)待辦事件的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03React替換傳統(tǒng)拷貝方法的Immutable使用
Immutable.js出自Facebook,是最流行的不可變數(shù)據(jù)結(jié)構(gòu)的實現(xiàn)之一。它實現(xiàn)了完全的持久化數(shù)據(jù)結(jié)構(gòu),使用結(jié)構(gòu)共享。所有的更新操作都會返回新的值,但是在內(nèi)部結(jié)構(gòu)是共享的,來減少內(nèi)存占用2023-02-02React?數(shù)據(jù)共享useContext的實現(xiàn)
本文主要介紹了React?數(shù)據(jù)共享useContext的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04react 項目 中使用 Dllplugin 打包優(yōu)化技巧
在用 Webpack 打包的時候,對于一些不經(jīng)常更新的第三方庫,比如 react,lodash,vue 我們希望能和自己的代碼分離開,這篇文章主要介紹了react 項目 中 使用 Dllplugin 打包優(yōu)化,需要的朋友可以參考下2023-01-01React Native全面屏狀態(tài)欄和底部導(dǎo)航欄適配教程詳細(xì)講解
最近在寫 React Native 項目,調(diào)試應(yīng)用時發(fā)現(xiàn)頂部狀態(tài)欄和底部全面屏手勢指示條區(qū)域不是透明的,看起來很難受。研究了一下這個問題,現(xiàn)在總結(jié)一下解決方案,這篇文章主要介紹了React Native全面屏狀態(tài)欄和底部導(dǎo)航欄適配教程2023-01-01