Categories
程式開發

一文搞懂ReactNative生命週期的進化


前言

眾所周知每個應用的開發框架都有其對應的生命週期函數,ReactNative是基於React開發的,所以其生命週期先關函數也和React一樣密不可分,為什麼文章標題叫“生命週期的進化”呢?這是有原因的,因為React在React 15和React 16兩個版本對生命週期函數做了優化調整,到底進行了那些調整和改進呢?讓我們隨著本文一探究竟。

React 15生命週期函數

下面這張圖是一個典型的React 15的生命週期函數流程圖,也是我們大多數開發者所了解到的。

一文搞懂ReactNative生命週期的進化 1

React 15相關的生命週期函數如下:

constructor()
componentWillReceiveProps()
shouldComponentUpdate()
componentWillMount()
componentWillUpdate()
componentDidUpdate()
componentDidMount()
render()
componentWillUnmount()

Mounting階段:組件初始化渲染

初始化渲染階段主要涉及如下幾個生命週期函數:

constructor()
componentWillMount()
render()
componentDidMount()

constructor()這個函數只在組件初始化的調用一次,通常開發中主要用於對state的數據初始化。

componentWillMount(),componentDidMount()函數在組件初始化階段也只調用一次,componentWillMount()在render()函數執行之前被調用,有些同學開發中會在此函數中做網絡請求,想想會有哪些風險呀?

之後觸發render()方法進行渲染頁面,但此時不會去操作真實DOM,只是把要渲染的頁面返回處理,真正處理是通過ReactDOM.render方法在頁面掛在階段完成的。

componentDidMount()函數會在真實DOM掛載完成時候調用,通常開發中我們在此函數進行網絡請求,消息監聽等來操作真實的DOM。

Updating階段:組件更新

組件的更新分為兩種:一種是父組件的更新觸發的更新,另一種是組件自身調用this.setState()觸發的更新。

組件更新過程中涉及的如下生命週期函數:

componentWillReceiveProps()
shouldComponentUpdate()
componentWillUpdate()
render()
componentDidUpdate()

首先看一下componentWillReceiveProps(nextProps),這個函數參數中nextProps表示接受到的新props,可以用來和this.props進行對比,看是否改變。

請注意,如果父組件導致組件重新渲染,即使props 沒有更改,也會調用此方法(componentWillReceiveProps)。如果只想處理更改,請確保進行當前值與變更值的比較。

>—— React官方文檔

關鍵點:也就是componentWillReceiveProps()函數不是props改變觸發的,而是由於父組件更新觸發的。

shouldComponentUpdate(nextProps, nextState)函數在組件自身的state發生改變時觸發,該函數的返回值決定了組件是否重新render,默認返回“true”,表示只要是state發生更新,就會重新render;實際開發工作中,我們一般會對比函數中的參數來進行業務邏輯判斷是否需要重新render。這也是一個React提供給我們的一個性能優化的方向之一。

componentWillUpdate()與componentDidUpdate()是在render前後進行觸發的,對應於componentWillMount()和componentDidMount()。 componentDidUpdate在組件更新完成之後觸發,可以操作真實DOM。

Unmounting階段:組件卸載

這個階段就比較簡單了,只有一個生命週期函數:

componentWillUnmount()

在組件卸載和銷毀之前會觸發componentWillUnmount()函數,實際開發中我們通常進行如下操作,比如清除定時器timer, 取消網絡請求或者清除在componentDidMount() 中創建的訂閱等。

進化:React 16生命週期函數

上面節選我們回顧了React 15的生命週期函數,那麼React 16有那些更新和改進呢?其中16.3版本和16.4版本的生命週期稍有不同,首先我們一起來16.3版本的流程圖React 16.3生命週期

一文搞懂ReactNative生命週期的進化 2

設計到的生命週期函數如下:

constructor()
getDerivedStateFromProps()
getSnapshotBeforeUpdate()
shouldComponentUpdate()
componentDidUpdate()
componentDidMount()
render()
componentWillUnmount()

Mounting階段:組件初始化階段(掛載)

在React 16的mounting階段涉及到的生命週期函數如下:

constructor()
getDerivedStateFromProps()
componentDidMount()
render()

React 16和React 15相比在初始化階段,多了一個getDerivedStateFromProps()函數,少了一個componentWillMount()函數。

那getDerivedStateFromProps()是用來替換componentWillMount()函數的嗎?

首先我們看一下這個函數

static getDerivedStateFromProps(props, state)

這是一個靜態的函數,並且需要返回一個對像用來更新state,如果返回null,則不會更新state。

關於此函數需要了解三個關鍵重要的點。

第一個這是一個靜態函數,所以在這個函數中不能訪問class的實例,也就是說不能在函數中使用this通過this.props的獲取數據。

第二個此函數接受兩個參數props和state,props是指父組件傳遞的props,state指的是自身的state。

第三個此函數需要一個對象格式的返回值,因為需要根據這個返回的數據來更新state,如果不需要更新state,就返回一個null, 如果什麼都不返回,就會收到警告,或者乾脆不重新這個函數。

Updating階段:組件更新階段

在更新階段涉及到的函數有一下幾個:

getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()

在更新階段,16.3和16.4稍微有些不同,來看一下流程圖

一文搞懂ReactNative生命週期的進化 3

對比16.3和16.4的流程圖我們發現,變化的部分是getDerivedStateFromProps()的觸發時機,主要的變化體現在

如下兩個方面:

1.React 16.4版本中getDerivedStateFromProps()在父組件更新接受props,組件自身調用setState()函數以及forceUpdate()函數執行時都會被觸發

2.React 16.3在更新階段只有父組件更新才會觸發。

對比React 15,我們發現React 16版本少了componentWillReceiveProps()以及componentWillUpdate(),增加了getDerivedStateFromProps()和getSnapshotBeforeUpdate()。

我們知道React 15中父組件發生更新後會觸發componentWillReceiveProps()函數,而自身setState的時候不會觸發,而在React 16中組件任何更新都會觸發getDerivedStateFromProps()函數,這種改進是為什麼呢?

對於getDerivedStateFromProps,React給出的解釋是:

與componentDidUpdate 一起,這個新的生命週期涵蓋過時componentWillReceiveProps 的所有用例。

可以看出這個新函數不僅僅是componentWillReceiveProps的簡單替代,其方法被定義成了static,即在方法內部能不能訪問this,那麼就能夠避免錯誤的操作,比如使用this.fetch進行網絡請求,使用this .setSate進行render造成死循環。

因此getDerivedStateFromProps 的存在只有一個目的:讓組件在props 變化時更新state。

關於此函數的使用,官方也給出了建議:

注意: 舊的componentWillReceiveProps 和新的getDerivedStateFromProps方法都會給組件增加明顯的複雜性。這通常會導致bug。考慮 派生state 的簡單替代方法” 使組件可預測且可維護。

接下來看getSnapshotBeforeUpdate,React官方的解釋:

與componentDidUpdate 一起,這個新的生命週期涵蓋過時的componentWillUpdate 的所有用例。

getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate() 在最近一次渲染輸出(提交到DOM 節點)之前調用。它使得組件能在發生更改之前從DOM 中捕獲一些信息(例如,滾動位置)。此生命週期的任何返回值將作為參數傳遞給componentDidUpdate()。

在這個函數里面可以拿到更新前的真實DOM和更新後的最新props和state.

最後看一下componentDidUpdate函數

componentDidUpdate(prevProps, prevState, snapshot)

如果組件實現了getSnapshotBeforeUpdate() 生命週期(不常用),則它的返回值將作為componentDidUpdate() 的第三個參數“snapshot” 參數傳遞。否則此參數將為undefined。

Unmounting階段:組件卸載

在卸載階段React 16和React 15一樣,沒有發生變化,都只涉及一個生命週期函數:

componentWillUnmount()

componentWillUnmount() 中不應調用setState(),因為該組件將永遠不會重新渲染。組件實例卸載後,將永遠不會再掛載它。

生命週期進化的原因

React 的生命週期發生變化和改進的原因都是因為React 項目使用成為“Fiber”的核心架構重寫了React。

Fiber使原來React 15的同步渲染變成了異步渲染,避免阻塞React 主線程。

在React 16之前,我們使用setState更新組件,React都會生成一個新的虛擬DOM,通過與上一次的DOM進行diff對比後,再定向更新真實的DOM。這是一個同步渲染的遞歸過程,就如同走樓梯一樣。

一文搞懂ReactNative生命週期的進化 4

如果頁面的佈局複雜嵌套很深,那麼遞歸調用的時間就會很長,那麼的主線程就會被js一直佔用著,任何交互,佈局,渲染都會停止,那給用戶呈現的畫面就是很卡頓。

而使用Fiber重構之後就解決了這個問題,Fiber將漫長的更新任務進行切片成小任務。執行完一個小任務,就將主線程交換回去,看看是否有優先級更高的任務需要處理,這樣就避免了同步更新造成UI阻塞的問題。

使用Fiber進行切片後,異步的渲染任務就變成了可打斷的,執行過程就變成瞭如下的模式:

一文搞懂ReactNative生命週期的進化 5

使用Fiber背後的故事

在React 16以後,如下的幾個生命週期函數都被標記為不安全的,

componentWillMount
componentWillReceiveProps
componentWillUpdate

現在這幾個生命週期函數前都增加了“UNSAFE_”前綴,變成如下模樣:

UNSAFE_componentWillMount、
UNSAFE_componentWillReceiveProps
UNSAFE_componentWillUpdate

因為Fiber重構後,渲染變成了異步的,通過查看新的生命週期圖譜,這幾個方法都處於原來的render階段,也就是會出現重複調用的問題,比如說不合理的使用setState造成重複渲染死循環等。

總結

總的來說,React生命週期的進化都是為Fiber架構服務的,Fiber帶了異步渲染的機制,使生命週期變的更加純粹和可控,同時也減少了我們書寫代碼不規範造成的不必要的bug。