為何要在componentDidMount裡面傳送請求?

JHanLu發表於2019-02-23

在我們編寫React程式碼的時候,總會遇到這麼一個問題:請求介面並展示我們獲取到的資料。這聽起來很簡單,但是你有沒有想過一個問題:在何時進行進行網路請求才是最好的?我相信你們都會說,我當然知道,在 componentDidMount 中啊,因為這是React官方推薦的,不服上個圖

pic1

這一下你應該服了吧?服是服了,但為什麼是 componentDidMountconstructorcomponentWillMount不可以嗎?

首先我們來百度一下,這是一個最高讚的答案

pic2

總結一下:

  1. componentDidmount 是在元件完全掛載後才會執行,在此方法中呼叫setState 會觸發重新渲染,最重要的是,這是官方推薦的!

  2. constructor 呼叫是在一開始,元件未掛載,所以不能用。

  3. componentWillMount 呼叫在 constructor 後,在這裡的程式碼呼叫 setState 不會出發重新渲染,所以不用。

  4. 還有一個沒有出現在這裡但聽得最多的說法是:在 componentWillMount 裡進行網路請求會阻礙元件的渲染。

  5. 反正就是要在 componentDidmount 裡用!

說的好像挺有道理的,但是也感覺怪怪的,看的再多不如自己動手測試一下。首先測試一下 constructor

constructor

class Parent extends React.Component {
  constructor(props) {
    super(props);
    
    this.state = {
      text: 'plain text'
    };
    
    fetch('https://s.codepen.io')
      .then(res => this.setState({text: 'success'}))
      .catch(err => this.setState({text: 'error'}))
  }
  
  render() {
    return (
      <div>
        <h1>{this.state.text}</h1>
      </div>
    );
  }
}

ReactDOM.render(
  <Parent/>,
  document.getElementById('root')
);
複製程式碼

這裡看演示,state從一開始的plain text變成了error(因為跨域問題,請求無法成功,但是沒有關係)

pic3

所以上面說的constructor 呼叫時元件未掛載,所以不能用的說法是錯誤的,元件未掛載也可以傳送請求,這裡所影響的時間只有執行傳送請求的時間,然後元件接著渲染,等非同步資料返回後,再執行 setState,或許你會說,如果請求時間很短,在元件掛載之前就返回了怎麼辦,此時的 setState 還會起作用嗎?彆著急,這個問題後面會提到。

componentWillMount

將請求移到 componentWillMount


class Parent extends React.Component {
  constructor(props) {
    super(props);
    
    this.state = {
      text: 'plain text'
    };
  }
  
  componentWillMount() {
     fetch('https://s.codepen.io')
      .then(res => this.setState({text: 'success'}))
      .catch(err => this.setState({text: 'error'}))
  }
  
  render() {
    return (
      <div>
        <h1>{this.state.text}</h1>
      </div>
    );
  }
}

ReactDOM.render(
  <Parent/>,
  document.getElementById('root')
);


複製程式碼

這裡,可以看到,state也是從plain text 變成了error,嫌太快看不清楚的可以用setTimeout模擬一下。這就很奇怪了,不是說willMount裡面setState不會重新渲染嗎?不是說網路請求會阻塞元件的渲染嗎?然而都沒有,其實原理跟constructor是一樣的,所影響的時間只有執行傳送請求的時間,並不會阻塞元件的渲染,但不推薦使用 componentWillMount 是有其他的原因:

  1. 很重要的一點,React16.3後將會廢棄掉componentWillMount、componentWillReceiveProps 以及 componentWillUpdate 三個周期函式,直到React 17前還可以使用,不過會有一個警告。

  2. 跟服務端渲染有關係(同構),如果在 componentWillMount 裡獲取資料,fetch data會執行兩次,一次在服務端一次在客戶端,使用 componentDidMount 則沒有這個問題。

至於前面說到的資料在元件掛載前返回導致不生效的,這種情況並不會發生, 因為 setState 是將更新的狀態放進了元件的__pendingStateQueue佇列中,react並不會立即響應更新,會等到元件掛載完成後,再統一更新髒元件,見下圖

pic4

因此,從另外的角度看,放在constructor或者componentWillMount裡面反而會更加有效率。

React Fiber

感謝@名揚的提醒,React16引入了React Fiber的概念,導致了componentWillMountcomponentWillReceivePropsshouldComponentUpdatecomponentWillUpdate這些生命週期出現了可能被呼叫不止一次的可能,詳情參見React Fiber是什麼?,這應該也是上文提到的這些生命週期將會被廢棄的原因。

總結

  1. 資料獲取可以放在 constructor 或者 componentDidmount 中,不建議放在 componentWillMount。 但是為了更好的程式碼規範和可讀性,建議統一放在 componentDidmount

  2. 對於首次render沒有資料,可能導致出錯的。可以設定一個initial state,或者增加一個loading狀態,載入資料時展示一個spinner或者骨架圖都是比較常用的方案。

參考連結

  1. Where to Fetch Data: componentWillMount vs componentDidMount

  2. React資料獲取為什麼一定要在componentDidMount裡面呼叫?

  3. React Fiber是什麼?

相關文章