沐鳴註冊_深入理解 React useLayoutEffect 和 useEffect 的執行時機

我們先看下 react 官方文檔對這兩個 hook 的介紹,建立個整體認識

useEffect(create, deps):

該 Hook 接收一個包含命令式、且可能有副作用代碼的函數。在函數組件主體內(這裏指在 react 渲染階段)改變 DOM、添加訂閱、設置定時器、記錄日誌以及執行其他包含副作用的操作都是不被允許的,因為這可能會產生莫名其妙的 bug 並破壞 UI 的一致性。使用 useEffect 完成副作用操作。賦值給 useEffect 的函數會
在組件渲染到屏幕之後執行。你可以把 effect 看作從 react 的純函數式世界通往命令式世界的逃生通道。

useLayoutEffect(create, deps):

其函數簽名與 useEffect 相同,但它
會在所有的 DOM 變更之後同步調用 effect。可以使用它來讀取 DOM 布局並同步觸發重渲染。在瀏覽器執行繪製之前,useLayoutEffect 內部的更新計劃將被同步刷新。

注意加粗的字段,react 官方的文檔其實把兩個 hook 的執行時機說的很清楚,下面我們深入到 react 的執行流程中來理解下

問題

useEffect 和 useLayoutEffect 的區別?

useEffect 和 useLayoutEffect 哪一個與 componentDidMount,componentDidUpdate 的是等價的?

useEffect 和 useLayoutEffect 哪一個與 componentWillUnmount 的是等價的?

為什麼建議將修改 DOM 的操作里放到 useLayoutEffect 里,而不是 useEffect?

流程

react 在 diff 后,會進入到 commit 階段,準備把虛擬 DOM 發生的變化映射到真實 DOM 上

在 commit 階段的前期,會調用一些生命周期方法,對於類組件來說,需要觸發組件的 getSnapshotBeforeUpdate 生命周期,對於函數組件,此時會調度 useEffect 的 create destroy 函數

注意是調度,不是執行。在這個階段,會把使用了 useEffect 組件產生的生命周期函數入列到 react 自己維護的調度隊列中,給予一個普通的優先級,讓這些生命周期函數異步執行

// 可以近似的認為,react 做了這樣一步,實際流程中要複雜的多

setTimeout(() => {
      const preDestory = element.destroy;
      if (!preDestory) prevDestroy();
      const destroy = create();
      element.destroy= destroy;
}, 0);

隨後,就到了 react 把虛擬 DOM 設置到真實 DOM 上的階段,這個階段主要調用的函數是 commitWork,commitWork 函數會針對不同的 fiber 節點調用不同的 DOM 的修改方法,比如文本節點和元素節點的修改方法是不一樣的。

commitWork 如果遇到了類組件的 fiber 節點,不會做任何操作,會直接 return,進行收尾工作,然後去處理下一個節點,這點很容易理解,類組件的 fiber 節點沒有對應的真實 DOM 結構,所以就沒有相關操作

但在有了 hooks 以後,函數組件在這個階段,會同步調用上一次渲染時 useLayoutEffect(create, deps) create 函數返回的 destroy 函數

注意一個節點在 commitWokr 后,這個時候,我們已經把發生的變化映射到真實 DOM 上了

但由於 js 線程和瀏覽器渲染線程是互斥的,因為 js 虛擬機還在運行,即使內存中的真實 DOM 已經變化,瀏覽器也沒有立刻渲染到屏幕上

此時會進行收尾工作,同步執行對應的生命周期方法,我們說的componentDidMount,componentDidUpdate 以及 useLayoutEffect(create, deps) 的 create 函數都是在這個階段被同步執行

對於 react 來說,commit 階段是不可打斷的,會一次性把所有需要 commit 的節點全部 commit 完,至此 react 更新完畢,js 停止執行

瀏覽器把發生變化的 DOM 渲染到屏幕上,到此為止 react 僅用一次迴流、重繪的代價,就把所有需要更新的 DOM 節點全部更新完成

瀏覽器渲染完成后,瀏覽器通知 react 自己處於空閑階段,react 開始執行自己調度隊列中的任務,此時才開始執行 useEffect(create, deps) 的產生的函數

幾個問題

useEffect 和 useLayoutEffect 的區別?

useEffect 在渲染時是異步執行,並且要等到瀏覽器將所有變化渲染到屏幕後才會被執行。

useLayoutEffect 在渲染時是同步執行,其執行時機與 componentDidMount,componentDidUpdate 一致

對於 useEffect 和 useLayoutEffect 哪一個與 componentDidMount,componentDidUpdate 的是等價的?

useLayoutEffect,因為從源碼中調用的位置來看,useLayoutEffect的 create 函數的調用位置、時機都和 componentDidMount,componentDidUpdate 一致,且都是被 React 同步調用,都會阻塞瀏覽器渲染。

useEffect 和 useLayoutEffect 哪一個與 componentWillUnmount 的是等價的?

同上,useLayoutEffect 的 detroy 函數的調用位置、時機與 componentWillUnmount 一致,且都是同步調用。useEffect 的 detroy 函數從調用時機上來看,更像是 componentDidUnmount (注意React 中並沒有這個生命周期函數)。

為什麼建議將修改 DOM 的操作里放到 useLayoutEffect 里,而不是 useEffect?

可以看到在流程9/10期間,DOM 已經被修改,但但瀏覽器渲染線程依舊處於被阻塞階段,所以還沒有發生迴流、重繪過程。由於內存中的 DOM 已經被修改,通過 useLayoutEffect 可以拿到最新的 DOM 節點,並且在此時對 DOM 進行樣式上的修改,假設修改了元素的 height,這些修改會在步驟 11 和 react 做出的更改一起被一次性渲染到屏幕上,依舊只有一次迴流、重繪的代價。

如果放在 useEffect 里,useEffect 的函數會在組件渲染到屏幕之後執行,此時對 DOM 進行修改,會觸發瀏覽器再次進行迴流、重繪,增加了性能上的損耗。

來自:https://www.cnblogs.com/iheyunfei/archive/2020/06/08/13065047.html

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

鏈接: http://www.fly63.com/article/detial/9271