1. 程式人生 > >react中componentWillReceiveProps()在props不改變的時候也可能被呼叫

react中componentWillReceiveProps()在props不改變的時候也可能被呼叫

最近在寫react時,需要在props被改變時更新一些東西,所以使用了componentWillReceiveProps方法,但是卻發現該方法總是在各種沒有改變props的情況下被呼叫,覺得很奇怪,遂詢問我導師(我導師超厲害的!),我導師說這個方法確實有可能在props不改變的情況下被呼叫,所以需要在方法裡手動判斷一下this.props和nextProps是否相同,不相同了才執行我的更新方法。於是我抽空閱讀了一下官方的文件https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/update/component_will_receive_props.html

,研究了一下這個方法到底是怎麼回事。下面先看一下componentDidMount()方法與componentWillReceiveProps()方法的對比。

componentDidMount() 在生命週期中只會被呼叫一次:

  • 在最開始的render後被呼叫(在所有的子元素都已經被render之後,並且所有的子元素都已經被呼叫它們的componentDidMounts 之後)
  • 元件在被解除安裝後的render(這確實是一個新的生命週期)

componentWillReceiveProps() 在生命週期的第一次render後不會被呼叫,但是會在之後的每次render中被呼叫 = 當父元件再次傳送props。

在react的update life cycle中,不得不提到componentWillReceiveProps方法。 該方法在props被傳遞給元件例項時被呼叫。下面詳細地介紹了一下該方法。

該方法當props發生變化時執行,初始化render時不執行,在這個回撥函式裡面,你可以根據屬性的變化,通過呼叫this.setState()來更新你的元件狀態,舊的屬性還是可以通過this.props來獲取,這裡呼叫更新狀態是安全的,並不會觸發額外的render呼叫

componentWillReceiveProps: function(nextProps) {
  this.setState({
    likesIncreasing: nextProps.likeCount > this.props.likeCount
  });
}

傳遞 props

該方法最典型的用法是:當新的props被傳遞給元件時。例如,我們有一個Form元件和一個Person元件。 Form元件有一個<input />,允許使用者通過在其中輸入來更改名稱。 輸入繫結到onChange事件並設定狀態。狀態值作為props傳遞給Person元件。

Form.js

import React from 'react';
import Person from './Person';

export default class Form extends React.Component {
  constructor(props) {
    super(props);
    this.state        = { name: '' } ;
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event) {
    this.setState({ name: event.currentTarget.value });
  }

  render() {
    return (
      <div>
        <input type="text" onChange={ this.handleChange } />
        <Person name={ this.state.name } />
      </div>
    );
  }
}

當用戶在輸入框輸入時,會使得在Person元件上呼叫componentWillReceiveProps(nextProps)方法傳入新的prop值。我們可以通過呼叫this.props來獲得舊的props,並且新值是傳遞給該方法的nextProps引數。

更新 State

那麼為什麼我們需要componentWillReceiveProps? 因為在這裡我們能抽取出新的props並且更新內部狀態,比如我們可能有一些狀態,這些狀態是props的計算結果,那麼我們就可以在這個方法裡寫邏輯,使用this.setState()來儲存結果。 

Props may not change

注意:componentWillReceiveProps()方法的呼叫,並不意味著props的值被改變。

為什麼呢?資料可能在初始渲染和後續兩次更新之間發生變化,而React無法知道資料有沒有變化。 因此,React需要呼叫componentWillReceiveProps,因為元件需要被知會新的props(即使新props恰好與舊props相同)。

假設我們有個prop,稱為 data ,該prop是一個數組,如下所示:

// psuedo code
this.setState({ data: [1, 2, 3] });

<MyComponent data={ this.state.data } />

如果在程式中對該data陣列進行了 push() 操作,使得data陣列的內容發生了變化,但是此時陣列本身(引用)並沒有改變,那麼react便無法判斷是否該陣列內部內容發生了改變,因此為了避免很多問題,也為了避免要做花費很多的深層比較,react會呼叫componentWillReceiveProps()方法。

因此 componentWillReceiveProps() 方法允許我們去檢查、判斷是否進來的新props發生了變更,讓我們自己去做決定。我們只需要知道:當該方法被呼叫時,並不意味著props肯定會不同。(講得十分透徹,值得一讀。)

你也許認為React應該使用更聰明的檢查機制來判斷props是否相等,但是這樣會存在幾個問題:

  • 當舊的props和新的props實際上是相同的物理物件時(只更改了物件的內部值),由於引用是triple-equals-equal,所以進行相等性的檢查並不能告訴我們該值是否已更改,唯一可能的解決方案是建立資料的深層拷貝,然後再進行深入比較。但對於大型資料結構(特別是具有周期的資料結構)而言,所花費的代價可能過於昂貴。
  • props物件可能會包含 對函式的引用,這些函式可能會捕獲閉包中的變數。 而React無法窺視這些閉包,因此React無法拷貝它們or驗證相等性。
  • props物件可能包含對父物件渲染過程中重新例項化的物件的引用(即不是triple-equals-equal),但在概念上相等(即相同的鍵和相同的值)。深度比較(代價昂貴)可以檢測到這一點,除了函式,因為沒有可靠的方法來比較兩個函式以判斷它們是否在語義上等價。

Skipping this method

與 Mounting 階段的其他方法不同,並不是所有的更新階段的方法都會每次被呼叫。 例如,如果update僅僅是因為一次狀態更改而被觸發,那麼componentWillReceiveProps()將會被跳過。 回到我們上面的Form.js例子:

 // ...
 handleChange(event) {
    this.setState({ name: event.currentTarget.value });
  }

  render() {
    return (
      <div>
        <input type="text" onChange={ this.handleChange } />
        <Person name={ this.state.name } />
      </div>
    );
  }
// ...

當用戶在輸入框中輸入時,將會觸發一個setState()方法。這將在Form元件和Person元件中觸發Update phase。對於Form元件來說,沒有收到新的props,所以componentWillReceiveProps()將會被跳過。