React-生命週期雜記

菜的黑人牙膏發表於2019-02-12

前言

自從React釋出Fiber之後,更新速度日新月異,而生命週期也隨之改變,雖然原有的一些生命週期函式面臨廢棄,但理解其背後更新的機制也是一種學習

在這裡根據官方文件以及社群上其他優秀的文章進行一個對於生命週期的總結,大致上分為以下三個模組

  1. 新老生命週期的區別
  2. 為什麼資料獲取要在componentDidMount中進行
  3. 為什麼要改變生命週期

新老生命週期的區別

新的生命週期增加了static getDerivedStateFromProps()以及getSnapshotBeforeUpdate(),廢棄了原有的componentWillMount()componentWillUpdate()以及componentWillReceiveProps()
分別如以下圖

原生命週期:

image

新生命週期(圖引用自React v16.3之後的元件生命週期函式):

image

為什麼資料獲取要在componentDidMount中進行

作者一開始也喜歡在React的willMount函式中進行非同步獲取資料(認為這可以減少白屏的時間),後來發現其實應該在didMount中進行。

首先,分析一下兩者請求資料的區別:

componentWillMount獲取資料:

  1. 執行willMount函式,等待資料返回
  2. 執行render函式
  3. 執行didMount函式
  4. 資料返回, 執行render

didMount獲取資料:

  1. 執行willMount函式
  2. 執行render函式
  3. 執行didMount函式, 等待資料返回
  4. 資料返回, 執行render

很明顯,在willMount中獲取資料,可以節省時間(render函式和didMount函式的執行時間),但是為什麼我們還要在didMount中獲取資料

  1. 如果使用服務端渲染的話,willMount會在服務端和客戶端各自執行一次,這會導致請求兩次(接受不了~),而didMount只會在客戶端進行
  2. 在Fiber之後, 由於任務可中斷,willMount可能會被執行多次
  3. willMount會被廢棄,目前被標記為不安全
  4. 節省的時間非常少,跟其他的延遲情況相比,這個優化可以使用九牛一毛的形容(為了這麼一點時間而一直不跟進技術的發展,得不償失),並且render函式是肯定比非同步資料到達先執行,白屏時間並不能減少

關於第一點,如果你想在服務端渲染時先完成資料的展示再一次性給使用者,官方的推薦做法是用constructor代替willMount

為什麼要改變生命週期

從上面的生命週期的圖中可以看出,被廢棄的三個函式都是在render之前,因為fiber的出現,很可能因為高優先順序任務的出現而打斷現有任務導致它們會被執行多次

另外的一個原因則是,React想約束使用者,好的框架能夠讓人不得已寫出容易維護和擴充套件的程式碼,這一點又是從何談起,我們可以從新增加以及即將廢棄的生命週期分析入手

componentWillMoun

首先這個函式的功能完全可以使用componentDidMount和constructor來代替,非同步獲取的資料的情況上面已經說明了,而如果拋去非同步獲取資料,其餘的即是初始化而已,這些功能都可以在constructor中執行,除此之外,如果我們在willMount中訂閱事件,但在服務端這並不會執行willUnMount事件,也就是說服務端會導致記憶體洩漏

所以componentWillMount完全可以不使用,但使用者有時候難免因為各種各樣的情況(如作者犯渾)在componentWillMount中做一些操作,那麼React為了約束開發者,乾脆就拋掉了這個API

componentWillReceiveProps

在老版本的 React 中,如果元件自身的某個 state 跟其 props 密切相關的話,一直都沒有一種很優雅的處理方式去更新 state,而是需要在 componentWillReceiveProps 中判斷前後兩個 props 是否相同,如果不同再將新的 props 更新到相應的 state 上去。這樣做一來會破壞 state 資料的單一資料來源,導致元件狀態變得不可預測,另一方面也會增加元件的重繪次數。類似的業務需求也有很多,如一個可以橫向滑動的列表,當前高亮的 Tab 顯然隸屬於列表自身的狀態,但很多情況下,業務需求會要求從外部跳轉至列表時,根據傳入的某個值,直接定位到某個 Tab。 本段引用自React v16.3 版本新生命週期函式淺析及升級方案

為了解決這些問題,React引入了第一個新的生命週期

static getDerivedStateFromProps

可以先看一下兩者在使用上的區別:

原有的程式碼

image

新的程式碼

image

這樣看似乎沒有什麼改變,特別是當我們把this,tabChange也放在didUpdate中執行時(正確做法),完全沒有不同,但這也是我們一開始想說的,React通過API來約束開發者寫出更好的程式碼,而新的使用方法有以下的優點

  1. getDSFP是靜態方法,在這裡不能使用this,也就是一個純函式,開發者不能寫出副作用的程式碼
  2. 開發者只能通過prevState而不是prevProps來做對比,保證了state和props之間的簡單關係以及不需要處理第一次渲染時prevProps為空的情況
  3. 基於第一點,將狀態變化(setState)和昂貴操作(tabChange)區分開,更加便於 render 和 commit 階段操作或者說優化。

componentWillUpdate

與 componentWillReceiveProps 類似,許多開發者也會在 componentWillUpdate 中根據 props 的變化去觸發一些回撥。但不論是 componentWillReceiveProps 還是 componentWillUpdate,都有可能在一次更新中被呼叫多次,也就是說寫在這裡的回撥函式也有可能會被呼叫多次,這顯然是不可取的。與 componentDidMount 類似,componentDidUpdate 也不存在這樣的問題,一次更新中 componentDidUpdate 只會被呼叫一次,所以將原先寫在 componentWillUpdate 中的回撥遷移至 componentDidUpdate 就可以解決這個問題。本段引用自React v16.3 版本新生命週期函式淺析及升級方案

另外一種情況則是我們需要獲取DOM元素狀態,但是由於在fiber中,render可打斷,可能在willMount中獲取到的元素狀態很可能與實際需要的不同,這個通常可以使用第二個新增的生命函式的解決

getSnapshotBeforeUpdate

getSnapshotBeforeUpdate(prevProps, prevState) // 返回的值作為componentDidUpdate的第三個引數
複製程式碼

與willMount不同的是, getSnapshotBeforeUpdate會在最終確定的render執行之前執行,也就是能保證其獲取到的元素狀態與didUpdate中獲取到的元素狀態相同,這裡官方提供了一段參考程式碼:

image

總結

隨著React Fiber的落地,許多功能都將開始改變,但本質上是換湯不換藥,很多時候都是React為了開發者寫出更好的程式碼而做的改變,當然這也是React的厲害之處,通過框架來約束開發者!

相關文章