React 深入系列4:元件的生命週期

艾特老幹部發表於2018-04-27

React 深入系列,深入講解了React中的重點概念、特性和模式等,旨在幫助大家加深對React的理解,以及在專案中更加靈活地使用React。

元件是構建React應用的基本單位,元件需要具備資料獲取、業務邏輯處理、以及UI呈現的能力,而這些能力是要依賴於元件不同的生命週期方法的。元件的生命週期分為3個階段:掛載階段、更新階段、解除安裝階段,每個階段都包含相應的生命週期方法。因為是深入系列文章,本文不會仔細介紹每個生命週期方法的使用,而是會重點講解在使用元件生命週期時,經常遇到的疑問和錯誤使用方式。

伺服器資料請求

初學者在使用React時,常常不知道何時向伺服器傳送請求,獲取元件所需資料。對於元件所需的初始資料,最合適的地方,是在componentDidMount方法中,進行資料請求,這個時候,元件完成掛載,其代表的DOM已經掛載到頁面的DOM樹上,即使獲取到的資料需要直接操作DOM節點,這個時候也是絕對安全的。有些人還習慣在constructor或者componentWillMount中,進行資料請求,認為這樣可以更快的獲取到資料,但它們相比componentDidMount的執行時間,提前的時間實在是太微乎其微了。另外,當進行伺服器渲染時(SSR),componentWillMount是會被呼叫兩次的,一次在伺服器端,一次在客戶端,這時候就會導致額外的請求發生。

元件進行資料請求的另一種場景:由父元件的更新導致元件的props發生變化,如果元件的資料請求依賴props,元件就需要重新進行資料請求。例如,新聞詳情元件NewsDetail,在獲取新聞詳情資料時,需要傳遞新聞的id作為引數給伺服器端,當NewsDetail已經處於掛載狀態時,如果點選其他新聞,NewsDetail的componentDidMount並不會重新呼叫,因而componentDidMount中進行新聞詳情資料請求的方法也不會再次執行。這時候,應該在componentWillReceiveProps中,進行資料請求:

componentWillReceiveProps(nextProps) {
  if(this.props.newId !== nextProps.newsId) {
    fetchNewsDetailById(nextProps.newsId)  // 根據最新的新聞id,請求新聞詳情資料
  }
}
複製程式碼

如果進行資料請求的時機是由頁面上的互動行為觸發的,例如,點選查詢按鈕後,查詢資料,這時只需要在查詢按鈕的事件監聽函式中,執行資料請求即可,這種情況一般是不會有疑問的。

更新階段方法的呼叫

元件的更新是元件生命週期中最複雜的階段,也是涉及到最多生命週期方法的階段。

正常情況下,當元件發生更新時,元件的生命週期方法的呼叫順序如下:

componentWillReceiveProps -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate

// 元件收到新的props(props中的資料並不一定真正發生變化)-> 決定是否需要繼續執行更新過程 -> 元件代表的虛擬DOM即將更新 -> 元件重新計算出新的虛擬DOM -> 虛擬DOM對應的真實DOM更新到真實DOM樹中
複製程式碼

父元件發生更新或元件自身呼叫setState,都會導致元件進行更新操作。父元件發生更新導致的元件更新,生命週期方法的呼叫情況同上所述。如果是元件自身呼叫setState,導致的元件更新,其生命週期方法的呼叫情況如下:

shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
複製程式碼

可見,這種情況下componentWillReceiveProps並不會被呼叫。

當元件的shouldComponentUpdate返回false時,元件會停止更新過程,這時候生命週期方法的呼叫順序如下:

componentWillReceiveProps -> shouldComponentUpdate -> 結束
複製程式碼

或(元件自身呼叫setState,導致的元件更新):

shouldComponentUpdate -> 結束
複製程式碼

setState的時機

元件的生命週期方法眾多,哪些方法中可以呼叫setState更新元件狀態?哪些方法中不可以呢?

  • 可以的方法

    componentWillMount、componentDidMount、componentWillReceiveProps、componentDidUpdate

    這裡有幾個注意點:

    1. componentWillMount 中同步呼叫setState不會導致元件進行額外的渲染,元件經歷的生命週期方法依次是componentWillMount -> render -> componentDidMount,元件並不會因為componentWillMount中的setState呼叫再次進行更新操作。如果是非同步呼叫setState,元件是會進行額外的更新操作。不過實際場景中很少在componentWillMount中呼叫setState,一般可以通過直接在constructor中定義state的方式代替。
    2. 一般情況下,當呼叫setState後,元件會執行一次更新過程,componentWillReceiveProps等更新階段的方法會再次被呼叫,但如果在componentWillReceiveProps中呼叫setState,並不會額外導致一次新的更新過程,也就是說,當前的更新過程結束後,componentWillReceiveProps等更新階段的方法不會再被呼叫一次。(注意,這裡仍然指同步呼叫setState,如果是非同步呼叫,則會導致元件再次進行渲染)
    3. componentDidUpdate中呼叫setState要格外小心,在setState前必須有條件判斷,只有滿足了相應條件,才setState,否組元件會不斷執行更新過程,進入死迴圈。因為setState會導致新一次的元件更新,元件更新完成後,componentDidUpdate被呼叫,又繼續setState,死迴圈就產生了。
  • 不可以的方法

    其他生命週期方法都不能呼叫setState,主要原因有兩個:

    1. 產生死迴圈。例如,shouldComponentUpdate、componentWillUpdate 和 render 中呼叫setState,元件本次的更新還沒有執行完成,又會進入新一輪的更新,導致不斷迴圈更新,進入死迴圈。
    2. 無意義。componentWillUnmount 呼叫時,元件即將被解除安裝,setState是為了更新元件,在一個即將解除安裝的元件上更新state顯然是無意義的。實際上,在componentWillUnmount中呼叫setState也是會丟擲異常的。

render次數 != 瀏覽器介面更新次數

先看下面的一個例子:

class App extends React.Component {

  constructor(props) {
    super(props)
    this.state = {
      bgColor: "red"
    }
  }

  render() {
    var {bgColor} = this.state
    return (
      <div style = {{backgroundColor: bgColor}}> 
        Test
      </div>
    );
  }
  
  componentDidMount() {
    this.setState({
      bgColor: "yellow"
    })
  }
}
複製程式碼

當我們觀察瀏覽器渲染出的頁面時,頁面中Test所在div的背景色,是先顯示紅色,再變成黃色呢?還是直接就顯示為黃色呢?

答案是:直接就顯示為黃色!

這個過程中,元件的生命週期方法被呼叫的順序如下:

constructor -> componentWillMount -> render -> componentDidMount -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
複製程式碼

元件在掛載完成後,因為setState的呼叫,將立即執行一次更新過程。雖然render方法被呼叫了兩次,但這並不會導致瀏覽器介面更新兩次,實際上,兩次DOM的修改會合併成一次瀏覽器介面的更新。React官網介紹componentDidMount方法時也有以下說明:

Calling setState() in this method will trigger an extra rendering, but it will happen before the browser updates the screen. This guarantees that even though the render() will be called twice in this case, the user won’t see the intermediate state.

這說明,元件render的次數 不一定等於 瀏覽器介面更新次數。雖然JS的執行和DOM的渲染分別由瀏覽器不同的執行緒完成,但JS的執行會阻塞DOM的渲染,而上面的兩次render是在一個JS事件週期內執行的,所以在兩次render結束前,瀏覽器不會更新介面。

下篇預告:

React 深入系列5:事件處理


我的新書《React進階之路》已上市,對React感興趣的同學不妨去了解下。 購買地址: 噹噹 京東

React 深入系列4:元件的生命週期

歡迎關注我的公眾號:老幹部的大前端

alt

相關文章