React-生命週期雜記

前言

自從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的厲害之處,通過框架來約束開發者!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章