在我們編寫React程式碼的時候,總會遇到這麼一個問題:請求介面並展示我們獲取到的資料。這聽起來很簡單,但是你有沒有想過一個問題:在何時進行進行網路請求才是最好的?我相信你們都會說,我當然知道,在 componentDidMount
中啊,因為這是React官方推薦的,不服上個圖
這一下你應該服了吧?服是服了,但為什麼是 componentDidMount
? constructor
或 componentWillMount
不可以嗎?
首先我們來百度一下,這是一個最高讚的答案
總結一下:
-
componentDidmount
是在元件完全掛載後才會執行,在此方法中呼叫setState
會觸發重新渲染,最重要的是,這是官方推薦的! -
constructor
呼叫是在一開始,元件未掛載,所以不能用。 -
componentWillMount
呼叫在constructor
後,在這裡的程式碼呼叫setState
不會出發重新渲染,所以不用。 -
還有一個沒有出現在這裡但聽得最多的說法是:在
componentWillMount
裡進行網路請求會阻礙元件的渲染。 -
反正就是要在
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(因為跨域問題,請求無法成功,但是沒有關係)
所以上面說的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
是有其他的原因:
-
很重要的一點,React16.3後將會廢棄掉componentWillMount、componentWillReceiveProps 以及 componentWillUpdate 三個周期函式,直到React 17前還可以使用,不過會有一個警告。
-
跟服務端渲染有關係(同構),如果在
componentWillMount
裡獲取資料,fetch data會執行兩次,一次在服務端一次在客戶端,使用componentDidMount
則沒有這個問題。
至於前面說到的資料在元件掛載前返回導致不生效的,這種情況並不會發生, 因為 setState
是將更新的狀態放進了元件的__pendingStateQueue佇列中,react並不會立即響應更新,會等到元件掛載完成後,再統一更新髒元件,見下圖
因此,從另外的角度看,放在constructor或者componentWillMount裡面反而會更加有效率。
React Fiber
感謝@名揚的提醒,React16引入了React Fiber
的概念,導致了componentWillMount
,componentWillReceiveProps
,shouldComponentUpdate
,componentWillUpdate
這些生命週期出現了可能被呼叫不止一次的可能,詳情參見React Fiber是什麼?,這應該也是上文提到的這些生命週期將會被廢棄的原因。
總結
-
資料獲取可以放在
constructor
或者componentDidmount
中,不建議放在componentWillMount
。 但是為了更好的程式碼規範和可讀性,建議統一放在componentDidmount
。 -
對於首次render沒有資料,可能導致出錯的。可以設定一個initial state,或者增加一個loading狀態,載入資料時展示一個spinner或者骨架圖都是比較常用的方案。