React v16.3之後的組件生命周期函數

React v16.3之後的組件生命周期函數

來自專欄進擊的React

React v16.3雖然是一個小版本升級,但是卻對React組件生命周期函數有巨大變化,這個話題其實應該早就講一講,一直忙,拖到今天才有機會和大家分享一下。

React v16.0剛推出的時候,是增加了一個componentDidCatch生命周期函數,這只是一個增量式修改,完全不影響原有生命周期函數;但是,到了React v16.3,大改動來了,引入了兩個新的生命周期函數:

  • getDerivedStateFromProps
  • getSnapshotBeforeUpdate

首先當然要吐槽的是React組件生命周期函數名都好長,完全程序員手打,真的很容易犯錯,不過,為了語義清晰嘛,也可以理解,而且我們應該都有各種方法在代碼編輯器中自動補齊,避免打字打錯的情況。而且,這次新的API getDerivedStateFromProps實際上就是用來取代以前的函數componentWillReceiveProps,getDerivedStateFromProps要比componentWillReceiveProps少打兩個字元,不虧:-)

先來看React v16.3之前的生命周期函數(圖中實際上少了componentDidCatch),如下圖。

圖片來自於https://hackernoon.com/reactjs-component-lifecycle-methods-a-deep-dive-38275d9d13c0?gi=630d5f23e5a

這個生命周期函數非常的對稱,有componentWilUpdate對應componentDidUpdate,有componentWillMount對應componentDidMount;也考慮到了因為父組件引發渲染可能要根據props更新state的需要,所以有componentWillReceiveProps。

但是,這個生命周期函數的組合在Fiber(React Fiber是什麼)之後就顯得不合適了,因為,如果要開啟async rendering,在render函數之前的所有函數,都有可能被執行多次。長期以來,原有的生命周期函數總是會誘惑開發者在render之前的生命周期函數做一些動作,現在這些動作還放在這些函數中的話,有可能會被調用多次,這肯定不是你想要的結果。

總有開發者問我,為什麼不在componentWillMount里寫AJAX獲取數據的功能,他們的觀點是,componentWillMount在render之前執行,早一點執行早得到結果。要知道,在componentWillMount里發起AJAX,不管多快得到結果也趕不上首次render,而且componentWillMount在伺服器端渲染也會被調用到(當然,也許這是預期的結果),這樣的IO操作放在componentDidMount里更合適。在Fiber啟用async render之後,更沒有理由在componentWillMount里做AJAX,因為componentWillMount可能會被調用多次,誰也不會希望無謂地多次調用AJAX吧。

道理說了都明白,但是歷史經驗告訴我們,不管多麼地苦口婆心教導開發者不要做什麼不要做什麼,都不如直接讓他們乾脆沒辦法做

隨著getDerivedStateFromProps的推出,同時deprecate了一組生命周期API,包括:

  • componentWillReceiveProps
  • componentWillMount
  • componentWillUpdate

可以看到,除了shouldComponentUpdate之外,render之前的所有生命周期函數全滅,就因為太多錯用濫用這些生命周期函數的做法,預期追求對稱的美學,不如來點實際的,讓程序員斷了在這些生命周期函數里做些不該做事情的念想。

至於shouldComponentUpdate,如果誰還想著在裡面做AJAX操作,那真的是沒救了。

按照官方說法,以前需要利用被deprecate的所有生命周期函數才能實現的功能,都可以通過getDerivedStateFromProps的幫助來實現。

這個getDerivedStateFromProps是一個靜態函數,所以函數體內不能訪問this,簡單說,就是應該一個純函數,純函數是一個好東西啊,輸出完全由輸入決定。

static getDerivedStateFromProps(nextProps, prevState) { //根據nextProps和prevState計算出預期的狀態改變,返回結果會被送給setState}

看到這樣的函數聲明,應該感受到React的潛台詞:老實做一個運算就行,別在這裡搞什麼別的動作

每當父組件引發當前組件的渲染過程時,getDerivedStateFromProps會被調用,這樣我們有一個機會可以根據新的props和之前的state來調整新的state,如果放在三個被deprecate生命周期函數中實現比較純,沒有副作用的話,基本上搬到getDerivedStateFromProps里就行了;如果不幸做了類似AJAX之類的操作,首先要反省為什麼自己當初這麼做,然後搬到componentDidMount或者componentDidUpdate裡面去。

所有被deprecate的生命周期函數,目前還湊合著用,但是只要用了,開發模式下會有紅色警告,在下一個大版本(也就是React v17)更新時會徹底廢棄。

React v16.3還引入了一個新的聲明周期函數getSnapshotBeforeUpdate,這函數會在render之後執行,而執行之時DOM元素還沒有被更新,給了一個機會去獲取DOM信息,計算得到一個snapshot,這個snapshot會作為componentDidUpdate的第三個參數傳入。

getSnapshotBeforeUpdate(prevProps, prevState) { console.log(#enter getSnapshotBeforeUpdate); return foo; } componentDidUpdate(prevProps, prevState, snapshot) { console.log(#enter componentDidUpdate snapshot = , snapshot); }

上面這段代碼可以看出來這個snapshot怎麼個用法,snapshot咋看還以為是組件級別的某個「快照」,其實可以是任何值,到底怎麼用完全看開發者自己,getSnapshotBeforeUpdate把snapshot返回,然後DOM改變,然後snapshot傳遞給componentDidUpdate。

官方給了一個例子,用getSnapshotBeforeUpdate來處理scroll,坦白說,我也想不出其他更常用更好懂的需要getSnapshotBeforeUpdate的例子,這個函數應該大部分開發者都用不上(聽得懂我的潛台詞嗎:不要用!)

所以,React v16.3之後的生命周期函數一覽圖成了這樣。

圖片來自於 https://medium.com/@baphemot/understanding-react-react-16-3-component-life-cycle-23129bc7a705

可以注意到,說getDerivedStateFromProps取代componentWillReceiveProps是不準確的,因為componentWillReceiveProps只在Updating過程中才被調用,而且只在因為父組件引發的Updating過程中才被調用(往上翻看第一個圖);而getDerivedStateFromProps在Updating和Mounting過程中都會被調用。

此外,從上面這個也看得出來,同樣是Updating過程,如果是因為自身setState引發或者forceUpdate引發,而不是不由父組件引發,那麼getDerivedStateFromProps也不會被調用。

這其實容易引發一些問題,不用仔細想,光是由此讓開發者不得不理解這亂七八糟的差異,就可以知道這是一個大坑!

還好,React很快意識到這個問題,在React v16.4中改正了這一點,改正的結果,就是讓getDerivedStateFromProps無論是Mounting還是Updating,也無論是因為什麼引起的Updating,全部都會被調用。

這樣簡單多了!

所以,上面的生命周期函數一覽圖要改一改。

修正後的生命周期函數圖

總結一下:

用一個靜態函數getDerivedStateFromProps來取代被deprecate的幾個生命周期函數,就是強制開發者在render之前只做無副作用的操作,而且能做的操作局限在根據props和state決定新的state,而已。

這是進一步施加約束,防止開發者亂來,我說過,施加約束的哲學指導思想,是我最愛React的原因。


推薦閱讀:

Redux的性能問題
前端每周清單第 22 期:ES8 正式發布、React 與 GraphQL 開發指南和性能優化,Vue.js 2.4.0 發布
dva系列3 - UI與數據邏輯的粘合層
創建 React 動畫的五種方式
2017 年底如何比較 Angular 4, React 16, Vue 2 的開發和運行速度?

TAG:React | 前端框架 | 前端開發 |