談談 React 新的生命週期鉤子

六面體混凝土移動師發表於2019-03-03

在 React 16.3 中,Facebook 的工程師們給 React 帶來一系列的新的特性,如 suspense 和 time slicing 等,這些都為 React 接下來即將到來的非同步渲染機制做準備,有興趣的可以看 Sophie Alpert 在 JSConf Iceland 2018 的演講

像 time slicing 等 React 內部優化特性,在 API 層面不會有太大變化,而 API 層面最大的變化,應該在生命週期鉤子。

React 的生命週期 API 一直以來十分穩定,但是當 React 團隊在引入非同步渲染機制的時候,發現之前的生命週期會的使用產生一些問題,所以才會改動生命週期 API,感興趣的可以看這篇部落格

在 React 16.3 中,為下面三個生命週期鉤子加上了 UNSAFE 標記:

  • UNSAFE_componentWillMount
  • UNSAFE_componentWillReceiveProps
  • UNSAFE_componentWillUpdate

新增了下面兩個生命週期方法:

  • static getDerivedStateFromProps
  • getSnapshotBeforeUpdate

在目前16.X(X>3)的 React 中,使用 componentWillMount, componentWillReceiveProps, and componentWillUpdate 這三個方法會收到警告。React 團隊計劃在 17.0 中測地廢棄掉這幾個 API

新的生命週期鉤子: static getDerivedStateFromProps

class Example extends React.Component {
  static getDerivedStateFromProps(props, state) {
    // ...
  }
}
複製程式碼

React 在例項化元件之後以及重新渲染元件之前,將呼叫新的靜態 getDerivedStateFromProps 生命週期方法。該方法類似於 componentWillReceiveProps,可以用來控制 props 更新 state 的過程。它返回一個物件表示新的 state。如果不需要更新元件,返回 null 即可。

getDerivedStateFromProps 與 componentDidUpdate一起將會替換掉所有的 componentWillReceiveProps。

新的生命週期鉤子: getSnapshotBeforeUpdate

class Example extends React.Component {
  getSnapshotBeforeUpdate(prevProps, prevState) {
    // ...
  }
}
複製程式碼

getSnapshotBeforeUpdate 方法在 React 對檢視做出實際改動(如 DOM 更新)發生前被呼叫,返回值將作為 componentDidUpdate 的第三個引數。

getSnapshotBeforeUpdate 配合 componentDidUpdate 可以取代 componentWillUpdate。

為何移除 componentWillMount

因為在 React 未來的版本中,非同步渲染機制可能會導致單個元件例項可以多次呼叫該方法。很多開發者目前會將事件繫結、非同步請求等寫在 componentWillMount 中,一旦非同步渲染時 componentWillMount 被多次呼叫,將會導致:

  • 進行重複的時間監聽,無法正常取消重複的 Listener,更有可能導致記憶體洩漏
  • 發出重複的非同步網路請求,導致 IO 資源被浪費
  • 在服務端渲染時,componentWillMount 會被呼叫,但是會因忽略非同步獲取的資料而浪費 IO 資源

現在,React 推薦將原本在 componentWillMount 中的網路請求移到 componentDidMount 中。至於這樣會不會導致請求被延遲發出影響使用者體驗,React 團隊是這麼解釋的:

There is a common misconception that fetching in componentWillMount lets you avoid the first empty rendering state. In practice this was never true because React has always executed render immediately after componentWillMount. If the data is not available by the time componentWillMount fires, the first render will still show a loading state regardless of where you initiate the fetch. This is why moving the fetch to componentDidMount has no perceptible effect in the vast majority of cases.

componentWillMount、render 和 componentDidMount 方法雖然存在呼叫先後順序,但在大多數情況下,幾乎都是在很短的時間內先後執行完畢,幾乎不會對使用者體驗產生影響。

為何移除 componentWillUpdate

大多數開發者使用 componentWillUpdate 的場景是配合 componentDidUpdate,分別獲取 rerender 前後的檢視狀態,進行必要的處理。但隨著 React 新的 suspense、time slicing、非同步渲染等機制的到來,render 過程可以被分割成多次完成,還可以被暫停甚至回溯,這導致 componentWillUpdate 和 componentDidUpdate 執行前後可能會間隔很長時間,足夠使使用者進行互動操作更改當前元件的狀態,這樣可能會導致難以追蹤的 BUG。

React 新增的 getSnapshotBeforeUpdate 方法就是為了解決上述問題,因為 getSnapshotBeforeUpdate 方法是在 componentWillUpdate 後(如果存在的話),在 React 真正更改 DOM 前呼叫的,它獲取到元件狀態資訊更加可靠。

除此之外,getSnapshotBeforeUpdate 還有一個十分明顯的好處:它呼叫的結果會作為第三個引數傳入 componentDidUpdate,避免了 componentWillUpdate 和 componentDidUpdate 配合使用時將元件臨時的狀態資料存在元件例項上浪費記憶體,getSnapshotBeforeUpdate 返回的資料在 componentDidUpdate 中用完即被銷燬,效率更高。

總結

React 近來 API 變化十分大,React 團隊很長時間以來一直在實現非同步渲染機制,目前的特性只是為非同步渲染做準備,預計 React 在 17 版本釋出時,效能會取得巨大的提升,期待中。。。

PS:從 Sophie Alpert 演示的兩個 DEMO 上看,非同步渲染的高效確實十分驚豔,有興趣的可以看文章開頭的演講。

我的所有文章都彙總在我的 GitHub: HuQingyang (胡青楊) · GitHub ,歡迎關注、交流、拍磚、搞基

相關文章