請解釋 React 生命週期?
2022年11月16日
了解 React 生命週期不僅是學習 React 最基本的概念之一,也是面試經典問題。本文會介紹三大生命週期、以及在 Class Component 中常用的生命週期方法。
React 元件生命週期分為三大: mounting、updating、unmounting。如果是使用 class component ,我們可以透過 React 提供出來的一些方法,在元件的生命週期執行程式碼,以下提供一些常見的操作方法(注意,這些方法是 class component 的生命週期方法,如果是用 functional component 的話,概念就不一樣)
生命週期:mounting、updating、unmounting
Mounting
當 React 首次渲染元件,並把渲染後的節點加入 DOM 中時,我們稱這時為 mounting。當 React 把節點加入到 DOM 後,瀏覽器會渲染並繪製畫面。在這個階段,生命週期將會依照下列的順序呼叫這些方法:
Updating
在 mounting 後,如果一個元件的 prop 或 state 有變化時,就會觸發重新渲染。重新渲染後,React 會去比較虛擬 DOM 有哪些地方改變了,然後將改變的部分更新到實際的 DOM 上。這個階段我們稱它為 updating。當一個 component 被重新渲染時,其生命週期將會依照下列的順序呼叫這些方法:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
Unmounting
當一個元件被從 DOM 中移除時,我們稱之為 unmounting。這時將會呼叫:
Render 與 Commit
Mounting、Updating 和 Unmounting 這三個不同的生命週期中,分別會再細分 Render 與 Commit 這兩個階段。我們用這三個階段來重新審視剛剛談論的生命週期
Mounting
在 mounting 的 render 階段,React 會呼叫根元件,並渲染該元件,如果該元件內有其他子元件,React 會遞迴地渲染。特別注意,為了能夠實現 concurrent 的特點,render 階段可能會被 React 暫停,中止或重新啟動 (例如有另一個元件的優先順序比較重要,React 會暫停比較不重要的,先 render 比較重要的,之後再回來 render 比較不重要的),因為要確保暫停與重新啟動不會影響 render 出的結果,React 的元件必須是純函式 (pure function),意即不能有副作用 (side effect),且在每次呼叫時都會回傳同樣的結果。
這也是為什麼,如果在 React 當中要使用到副作用,我們必須放在 componentDidMount
這個生命週期方法 (或 useEffect
),在 render 時確保是純函式,有副作用,就等 mounting 完成後,透過 componentDidMount
來執行。(關於純函式的詳細說明,可參考《什麼是純函式 (pure function)? 為什麼 React 的函式元件需要是純函式?
》一文)。
至於在 mounting 的 commit 階段,React 會透過 appendChild()
這個 DOM API,把 render 出的結果,加入到 DOM 上面,然後實際顯示到畫面中。
Updating
在 updating 的 render 階段,React 會呼叫觸發更新的元件。這個過程是遞歸的,如果被呼叫的元件回傳另一個元件,那 React 會再呼叫另一個元件,一直持續下去,直到沒有元件被回傳。
而到了 commit 階段,React 會去比較 render 前與後的兩個虛擬 DOM,然後把差異的部分,更新到實際的 DOM 上面。跟 mounting 時一樣,在 updating 時,要確保沒有副作用,如果要使用副作用,可以使用 componentDidUpdate()
這個生命週期方法 (或 useEffect
)。
Updating 時還有 pre-commit 階段
上一段落談到 render 與 commit 階段。如果要更細節地討論,在元件的 updating 時還會有一個 pre-commit 階段。顧名思義這個階段發生在實際 commit 之前 (實際把更新後的結果同步到 DOM 之前)。
在 updating 的 pre-commit 階段,如果使用 class component,getSnapshotBeforeUpdate()
方法會被觸發。這個方法讓我們可以在 DOM 被真正改變之前先從其中抓取一些資訊 (例如:滾動的位置)。之所以需要這個方法,是因為在 updating 時,如果 render 階段有異步操作,那麼 render 階段到 commit 階段之間,可能不會是馬上發生。換句話說,如果在這兩個階段間,網站的使用者做了某個操作 (例如調整瀏覽器的視窗大小),那麼在 render 階段,透過 componentWillUpdate
拿到的 DOM 資訊可能已經不是精確的。所以透過 getSnapshotBeforeUpdate
讓開發者能拿到更精確的資訊。
除非是要做動畫類的操作,getSnapshotBeforeUpdate()
幾乎很少用到。另外,如果是使用 functional component,React 目前還沒有提供可以模擬相對應的 Hook 方法 (詳見此討論)。