React函數式元件渲染、useEffect順序總結

2023-07-24 21:00:45

參考資料:
深入React的生命週期(上):出生階段(Mount)
深入React的生命週期(下):更新(Update)
精讀《useEffect 完全指南》
React元件重新渲染理解 & 優化大全
React渲染順序及useEffect執行順序探究(含並行模式)

元件狀態

同樣還是可以把元件的狀態分為mount、update和unmount。

  • mount:元件首次出現在頁面中。React會通過最後元件的函數返回值,確定它有哪些子元件,依次mount和render子元件。一個component只是定義了,但沒有最後被返回,就不會被掛載和渲染。
  • update:元件的重新渲染,重新渲染的條件見這裡
  • unmount:如果元件從頁面消失,就會被unmount。常見於條件渲染、或者子元件未使用key標明時的位置更改。

React16的元件渲染過程

即使用ReactDOM.createRoot(DOM).render(<App />)渲染元件時。

單一元件渲染過程

在元件的不同階段,呼叫順序如下

mount

  • 函數體:此時useState等hooks取初始值,如果用callback初始化,則會呼叫初始化函數
  • effect函數:會呼叫一遍所有useEffect註冊的函數,呼叫順序就是useEffect在函數體裡出現的順序

update

  • 函數體:正常呼叫,取最新state和ref的值

  • clean函數:如果依賴項A=[…]發生改變,則會呼叫,但若有其它依賴項B也變了,卻沒列進依賴項裡,這些未註冊依賴項會使用最後一次A=[…]發生改變時的B的值。因為這是clean函數最新的定義。

    樣例可見React函數式元件渲染順序探究(Demo),元件依賴了name和state,但只註冊了state這一個依賴項。

  • effect函數:如果依賴項A=[…]發生改變,則會呼叫,但若有其它依賴項B也變了,卻沒列進依賴項裡,這些未註冊依賴項會使用最後一次A=[…]發生改變時的B的值。因為這是effect函數最新的定義。

unmount

  • clean函數:會呼叫一遍所有useEffect返回的clean函數,呼叫順序也是註冊順序。同樣,也取的是它最新的定義。

    假設有兩個effect,都有未註冊依賴項B。但它們一個依賴項為A=[…],另一個為[]

    如果最開始B=1,而A變的時候B=2,最後unmount的時候,前者的B=2,後者的B=1,因為後者的clean函數並未更新。

樹型元件呼叫順序

mount

如果有一個這樣的component:

<A>
    <A1>
        <A1_1/>
        <A1_2/>
    </A1>
    <A2>
        <A2_1/>
        <A2_2/>
    </A2>
</A>
  • 函數體:呼叫順序是先序遍歷的DFS,即[A, A1, A1_1, A1_2, A2, A2_1, A2_2]
  • effect:類似於二元樹的後序遍歷,先遍歷孩子,再遍歷根,即:[A1_1, A1_2, A1, A2_1, A2_2, A2, A]

update

如果上述的component變成了如下,A重新渲染。

<A>
    <A2>
        <A2_1/>
        <A2_2/>
    </A2>
    <A1>
        <A1_1/>
        <A1_2/>
    </A1>
</A>
  • 如果A1和A2沒有設定key,React會當作需要unmount舊的A1、A2,再mount新的A1、A2。
  • 否則,React只會重新渲染A1、A2。

假設這裡A1設定了key,而A2沒有:

  • 函數體:按當前元件內容的先序DFS:[A1, A1_1, A1_2, A2, A2_1, A2_2]
  • clean:
    • 先執行unmount的元件的clean,執行順序是先序DFS,即[A2, A2_1, A2_2]
    • 再執行update元件的clean,執行順序是後序DFS,即[A1_1, A1_2, A1]
  • effect:按當前元件內容的後序DFS執行,即[A2_1, A2_2, A2, A1_1, A1_2, A1]

React18的更新

即使用StrictMode渲染元件時。

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

與16最大的區別是:

(1)函數體都會被呼叫2遍

(2)新mount的元件都會再次呼叫一遍clean和effect

有點類似於, mount (React 18)約等於mount(React 16) + update(React 16)

所以:

  • 第一次body即mount的body,第二次body是update的body

    但也不完全相同,比如,如果使用callback來初始化state的值時,mount的時候呼叫的兩遍函數體,都是會呼叫這個callback的,而不是一次呼叫一次不呼叫。

  • 第一次effect是mount的effect

  • 接下來的clean和effect是update時的clean+effect

mount

如果有一個這樣的component:

<A>
    <A1>
        <A1_1/>
        <A1_2/>
    </A1>
    <A2>
        <A2_1/>
        <A2_2/>
    </A2>
</A>
  • 函數體:呼叫順序是先序遍歷的DFS,即[A, A1, A1_1, A1_2, A2, A2_1, A2_2]。由於會叫兩遍,實際上是[A, A, A1, A1, A1_1, A1_1, A1_2, A1_2, A2, A2, A2_1, A2_1, A2_2, A2_2]
    • 有意思的是,不是先mount DFS一遍,update DFS一遍,是在DFS的過程中直接叫兩遍函數體。
  • effect:類似於二元樹的後序遍歷,先遍歷孩子,再遍歷根,即:[A1_1, A1_2, A1, A2_1, A2_2, A2, A]
  • clean:後序DFS[A1_1, A1_2, A1, A2_1, A2_2, A2, A]
  • effect:後序DFS[A1_1, A1_2, A1, A2_1, A2_2, A2, A]

update

如果上述的component變成了如下,A重新渲染。

<A>
    <A2>
        <A2_1/>
        <A2_2/>
    </A2>
    <A1>
        <A1_1/>
        <A1_2/>
    </A1>
</A>
  • 如果A1和A2沒有設定key,React會當作需要unmount舊的A1、A2,再mount新的A1、A2。
  • 否則,React只會重新渲染A1、A2。

假設這裡A1設定了key,而A2沒有:

  • 函數體:按當前元件內容的先序DFS:[A1, A1_1, A1_2, A2, A2_1, A2_2]。同樣會呼叫2次。
  • clean:
    • 先執行unmount的元件的clean,執行順序是先序DFS,即[A2, A2_1, A2_2]
    • 再執行update元件的clean,執行順序是後序DFS,即[A1_1, A1_2, A1]
  • effect:按當前元件內容的後序DFS執行,即[A2_1, A2_2, A2, A1_1, A1_2, A1]
  • clean:mount的元件會被update,所以會有第二輪clean,後序DFS,即[A2_1, A2_2, A2]
  • effect:第二輪effect,即[A2_1, A2_2, A2]