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