對(duì)于react生命周期的理解,反反復(fù)復(fù)有很多次不同的理解狭握,我就做個(gè)整理递览,以免每次都進(jìn)行重新推翻
按照官網(wǎng)的解釋組件的生命周期分成掛載叼屠,更新,卸載绞铃,以及錯(cuò)誤處理的幾個(gè)流程
16.3以前的生命周期分成
- 初始化階段constuctor,
- 掛載階段有componentWillMount,render ,componentDidMount
- 更新階段的流程是componentWillReceiveProps镜雨,shouldComponentUpdate,componentWillUpdate儿捧, render,componentDidUpdate
- 卸載 階段componentWillunMount
- 錯(cuò)誤處理 componentDidCatch()
16.4推出fibber之后菲盾,官網(wǎng)也說將在17.0開啟async rendering(異步渲染)
那么render之前的函數(shù)都將被執(zhí)行多次 所以
16.3以后新增getDerivedStateFromProp將逐漸替代render之前(除shouldComponentUpdate)的生命(componentWillMount,componentWillUpdate, componentWillReceiveProps)
- 掛載階段 constructor, getDerivedStateFromProps render componentDidMount
- 更新階段 getDerivedStateFromProps shouldComonentUpdate render getSnapshotBeforeUpdate componentDidUpdate
- 錯(cuò)誤處理 static getDerivedStateFromError() static getDerivedStateFromError()
componentWillMount
該生命是在組件掛載到dom之前會(huì)被調(diào)用 只調(diào)用一次,官網(wǎng)指明在這里用setstate不會(huì)引起組件重新渲染dom懒鉴,此方法是服務(wù)端渲染唯一會(huì)調(diào)用的生命周期函數(shù)
(我試了下在componentsWillMount直接調(diào)用setState。是可以讓渲染后拿到最新的state的值临谱,但是此時(shí)render只調(diào)用一次。就說明這里用setstate不會(huì)引起組件重新(第二次)渲染dom悉默。只是在render之前setState已經(jīng)將需要更新的加入隊(duì)列了。)這樣不算重新觸發(fā)渲染的更新是沒什么意義抄课,而這樣的初始化state應(yīng)該放在constuctor里面
componentDiDMount
組件掛載后唱星,(插入到Dom樹)之后會(huì)被調(diào)用跟磨。官網(wǎng)建議網(wǎng)絡(luò)數(shù)據(jù)請(qǐng)求,最適合放在這里吱晒。依賴dom節(jié)點(diǎn)的初始化應(yīng)該放在這個(gè)生命周期
但是不適合直接在這里調(diào)用setState(),因?yàn)閏omponentDidMount本身處于一次更新中沦童,我們又調(diào)用了一次setstate 就會(huì)在未來在執(zhí)行一次render 造成不必要的性能浪費(fèi)仑濒。所以不推薦直接在關(guān)于componentDidMount調(diào)用setstate叹话。 但是在componentDidMount可以條用接口,在回調(diào)中去修改setstate墩瞳。
官網(wǎng)也指明說驼壶,兩次渲染會(huì)發(fā)生在瀏覽器更新屏幕之前,但是不推薦喉酌。會(huì)導(dǎo)致性能問題热凹。
關(guān)于在哪個(gè)生命周期發(fā)起異步請(qǐng)求獲取頁面初始數(shù)據(jù)
。
那如果在這里發(fā)送異步請(qǐng)求拉去數(shù)據(jù)并且setState更新數(shù)據(jù)呢泪电,是不是可以比在componentDidMount減少一次渲染般妙,然后優(yōu)先提早拿到更新的數(shù)據(jù)呢?(官網(wǎng)不推薦)
state={
count:0
}
componentWillMount(){
console.log('willMount')
fetch('s.codepen.io')
.then(res =>{
this.setState({count: 'success'})
console.log('setdata')
})
.catch(err => this.setState({count: 'error'}))
}
componentDidMount(){
console.log('didMount')
}
render() {
console.log('render')
return <div>{this.state.count}</div>
}
//頁面最后顯示success 打印結(jié)果
// willMount
// render
// didMount
//setdata
//render
可以看出相速,render是在componentWillMount執(zhí)行之后馬上就被調(diào)用碟渺,所以此時(shí)由于異步請(qǐng)求還沒有拿到數(shù)據(jù)。等到異步請(qǐng)求拿到數(shù)據(jù)之后去setState突诬。會(huì)重新調(diào)用render,總的來說還是進(jìn)行了兩次渲染苫拍,異步請(qǐng)求之后的setState還是觸發(fā)了渲染更新。所以初始需要請(qǐng)求異步數(shù)據(jù)旺隙,放在這里也同樣需要render一次“加載中”的空數(shù)據(jù)狀態(tài)绒极。總的來說蔬捷,組件在首次渲染時(shí)總是會(huì)處于沒有異步數(shù)據(jù)的狀態(tài)垄提。
那么為什么建議在componentDiDMount異步獲取外部數(shù)據(jù)呢?
1抠刺、如果是服務(wù)端渲染塔淤,componentWillMount是唯一會(huì)執(zhí)行的生命周期,如果是服務(wù)端渲染速妖,在這里獲取數(shù)據(jù)(發(fā)送請(qǐng)求)可能會(huì)執(zhí)行兩次高蜂。一次是在服務(wù)端一次是在客戶端
2、如果在16.4之后增加了fiber罕容,使的整個(gè)React的生命周期分成兩個(gè)階段备恤,在第一階段的生命周期是可以被中斷的,每次中斷之后都會(huì)重新執(zhí)行第一階段得锦秒,而第二階段不能中斷露泊。一旦觸發(fā)第二階段,就一定要等到第二階段執(zhí)行完畢旅择,componentWillMount在第一階段惭笑,componentDidMount在第二階段,如果吧請(qǐng)求放在componentWillMount中則可能發(fā)送多次請(qǐng)求,
綜上所述所以放在componentDidMount中更合適
-
關(guān)于事件訂閱
一般情況下沉噩,如果在componentWillMount 中做訂閱外部事件捺宗,會(huì)在componentWillunMount中取消訂閱蚜厉,但是在服務(wù)端渲染的情況下畜眨,服務(wù)端是不會(huì)調(diào)用componentWillunMount,所以在服務(wù)端訂閱事件是會(huì)導(dǎo)致內(nèi)存泄露贰健。
另一方面聽上面的問題一樣早抠。在未來開啟React異步渲染之后,第一階段componentWillMount調(diào)用之后悬垃,組件的渲染還是有可能被其他事物中斷的甘苍,所以沒有辦法保證componentWillunMount可以被調(diào)用 。看彼。
componentDidMount不會(huì)有這個(gè)問題 所以添加訂閱也應(yīng)該在componentDidMount中
componentWillReceiveProps
componentWillReceiveProps(nextProps)
調(diào)用時(shí)機(jī):只有在父組件重新渲染的時(shí)候(就是已掛載的組件接收到新的props之前靖榕,(此時(shí)this.props訪問到的還是渲染之前的props))調(diào)用顽铸,不管父組件傳來的props有沒有改變。只要父組件重新渲染都會(huì)調(diào)用此方法谓松、
getDerivedStateFromProps
getDerivedStateFromProps(props,state)
是靜態(tài)方法,無權(quán)訪問組件實(shí)例娜膘,(即使無法使用this)在state更新或者props更新的時(shí)候都會(huì)調(diào)用組件竣贪,就是每次渲染前都會(huì)調(diào)用,這個(gè)與componentWillReceiveProps不同贾富。此方法適用于罕見案例,就是state的值在任何情況下都去取決于props。返回一個(gè)對(duì)象用來更新state淑际,返回null不更新任何內(nèi)容
需要優(yōu)化的點(diǎn):
-
基于 props 更新 state
一般如果是組件的state的值任何情況下都依賴于props的時(shí)候春缕,在16.3以后應(yīng)該拋棄componentWillReceiveProps,而使用getDerivedStateFromProps返回一個(gè)對(duì)象用來更新state.
static getDerivedStateFromProps(props, state) {
if (props.currentRow !== state.lastRow) {
return {
isScrollingDown: props.currentRow > state.lastRow,
lastRow: props.currentRow,
};
}
// 返回 null 表示無需更新 state票灰。
return null;
}
-
props 更新時(shí)獲取外部數(shù)據(jù)宅荤,props 更新的副作用
如果需要更新狀態(tài)以響應(yīng)props的更改,則可以通過用this.props 和nextProps進(jìn)行比較惹盼。在掛載的過程中手报,不會(huì)針對(duì)初始的props去調(diào)用改方法改化,
官方指出,如果要執(zhí)行副作用(數(shù)據(jù)提取和動(dòng)畫)請(qǐng)改用componentDidUpdate 揍鸟,在這之前很多時(shí)候都會(huì)用到redux存放props燥爷。如果有props更新引起的副作用。所以就會(huì)有
之前16.3之前大多數(shù)用
class ExampleComponent extends React.Component {
componentWillReceiveProps(nextProps) {
if (this.props.isVisible !== nextProps.isVisible) {
this._loadAsyncData(nextProps.isVisible);
}
}
}
static getDerivedStateFromProps(props, state) {
// 保存 prevId 在 state 中稚配,以便我們?cè)?props 變化時(shí)進(jìn)行對(duì)比道川。
// 清除之前加載的數(shù)據(jù)(這樣我們就不會(huì)渲染舊的內(nèi)容)。
if (props.id !== state.prevId) {
return {
externalData: null,
prevId: props.id,
};
}
// 無需更新 state
return null;
}
componentDidUpdate(prevProps, prevState) {
if (this.state.externalData === null) {
this._loadAsyncData(this.props.id);
}
}
與 componentWillUpdate 類似臊岸,componentWillReceiveProps 可能在一次更新中被多次調(diào)用尊流,也就是說寫在這里的副作用方法,異步請(qǐng)求逻住,回調(diào)函數(shù)也有可能會(huì)被調(diào)用多次瞎访,而此時(shí)與 componentDidMount 類似吁恍,componentDidUpdate 也不存在這樣的問題,一次更新中 componentDidUpdate 只會(huì)被調(diào)用一次伴奥,所以官網(wǎng)建議講 componentDidUpdate 就可以解決這個(gè)問題
- 同樣的props更新引起的副作用也應(yīng)該從componentWillReceiveProps 遷移到componentDidUpdate
- 以及props更新引起的調(diào)用外部回調(diào)咕幻。也應(yīng)該從componentWillUpdate遷移至componentDidUpdate
getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState)
調(diào)用時(shí)機(jī):會(huì)在最終的render之前被調(diào)用肄程,也就是getSnapshotBeforeUpdate中獲取到的dom元素狀態(tài)與componentWillUpdate的是一樣的,所以可以用 這個(gè)方法代替componentWillUpdate獲取組件更改之前捕獲一些dom信息(例如:滾動(dòng)高度玄叠、)
返回的一個(gè)值作為componentDidUpdate, 的第三個(gè)參數(shù)拓提。
配個(gè)componentDidUpdate。覆蓋componentWillUpdate的用法
-優(yōu)化的點(diǎn):在更新前記錄獲取原來的dom節(jié)點(diǎn)屬性
在沒有這個(gè)生命周期之前寺惫,一般會(huì)利用在componentWillUpdate讀取更新前dom元素狀態(tài)屬性蹦疑,但是在異步渲染中,render階段的生命周期(如 componentWillUpdate 和 render)和commoit階段的生命周期“”(componentDidUpdate)可能存在延遲艇肴、
官方提供了下面這個(gè)例子
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// 我們是否在 list 中添加新的 items ?
// 捕獲滾動(dòng)??位置以便我們稍后調(diào)整滾動(dòng)位置核畴。
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// 如果我們 snapshot 有值冲九,說明我們剛剛添加了新的 items,
// 調(diào)整滾動(dòng)位置使得這些新 items 不會(huì)將舊的 items 推出視圖咖刃。
//(這里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
參考官方文檔
[https://react.docschina.org/blog/2018/03/27/update-on-async-rendering.html#fetching-external-data]