在說新的生命周期之前盛卡,我們先了解下原來的生命周期:
- 掛載卸載過程
1.1.constructor()
constructor()中完成了React數(shù)據(jù)的初始化系任,它接受兩個參數(shù):props和context,當想在函數(shù)內(nèi)部使用這兩個參數(shù)時步氏,需使用super()傳入這兩個參數(shù)响禽。
注意:只要使用了constructor()就必須寫super(),否則會導致this指向錯誤。
1.2.componentWillMount()
componentWillMount()一般用的比較少荚醒,它更多的是在服務(wù)端渲染時使用芋类。它代表的過程是組件已經(jīng)經(jīng)歷了constructor()初始化數(shù)據(jù)后,但是還未渲染DOM時界阁。
1.3.componentDidMount()
組件第一次渲染完成侯繁,此時dom節(jié)點已經(jīng)生成,可以在這里調(diào)用ajax請求泡躯,返回數(shù)據(jù)setState后組件會重新渲染
1.4.componentWillUnmount ()
在此處完成組件的卸載和數(shù)據(jù)的銷毀贮竟。
- clear你在組建中所有的setTimeout,setInterval
- 移除所有組建中的監(jiān)聽 removeEventListener
- 有時候我們會碰到這個warning:
Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the undefined component.
原因:因為你在組件中的ajax請求返回setState,而你組件銷毀的時候丽焊,請求還未完成,因此會報warning
解決方法:
componentDidMount() {
this.isMount === true
axios.post().then((res) => {
this.isMount && this.setState({ // 增加條件ismount為true時
aaa:res
})
})
}
componentWillUnmount() {
this.isMount === false
}
- 更新過程
2.1. componentWillReceiveProps (nextProps)
- 在接受父組件改變后的props需要重新渲染組件時用到的比較多
- 接受一個參數(shù)nextProps
- 通過對比nextProps和this.props坝锰,將nextProps的state為當前組件的state粹懒,從而重新渲染組件
componentWillReceiveProps (nextProps) {
nextProps.openNotice !== this.props.openNotice&&this.setState({
openNotice:nextProps.openNotice
}重付,() => {
console.log(this.state.openNotice:nextProps)
//將state更新為nextProps,在setState的第二個參數(shù)(回調(diào))可以打 印出新的state
})
}
2.2.shouldComponentUpdate(nextProps,nextState)
- 主要用于性能優(yōu)化(部分更新)
- 唯一用于控制組件重新渲染的生命周期顷级,由于在react中,setState以后确垫,state發(fā)生變化弓颈,組件會進入重新渲染的流程,在這里return false可以阻止組件的更新
- 因為react父組件的重新渲染會導致其所有子組件的重新渲染删掀,這個時候其實我們是不需要所有子組件都跟著重新渲染的翔冀,因此需要在子組件的該生命周期中做判斷
2.3.componentWillUpdate (nextProps,nextState)
shouldComponentUpdate返回true以后,組件進入重新渲染的流程披泪,進入componentWillUpdate,這里同樣可以拿到nextProps和nextState纤子。
2.4.componentDidUpdate(prevProps,prevState)
組件更新完畢后,react只會在第一次初始化成功會進入componentDidmount,之后每次重新渲染后都會進入這個生命周期款票,這里可以拿到prevProps和prevState控硼,即更新前的props和state。
2.5.render()
render函數(shù)會插入jsx生成的dom結(jié)構(gòu)艾少,react會生成一份虛擬dom樹卡乾,在每一次組件更新時,在此react會通過其diff算法比較更新前后的新舊DOM樹缚够,比較以后幔妨,找到最小的有差異的DOM節(jié)點,并重新渲染谍椅。
接下來我們看看更新后的生命周期
先看看它的變化:
新增:getDerivedStateFromProps误堡,getSnapshotBeforeUpdate
UNSAFE:UNSAFE_componentWillMount,UNSAFE_componentWillUpdate雏吭,UNSAFE_componentWillReceiveProps
React 官方正式發(fā)布了 v16.3 版本锁施。在這次的更新中,除了前段時間被熱烈討論的新 Context API 之外思恐,新引入的兩個生命周期函數(shù) getDerivedStateFromProps沾谜,getSnapshotBeforeUpdate 以及在未來 v17.0 版本中即將被移除的三個生命周期函數(shù) componentWillMount,componentWillReceiveProps胀莹,componentWillUpdate .
getDerivedStateFromProps
React生命周期的命名一直都是非常語義化的基跑,這個生命周期的意思就是從props中獲取state,可以說是太簡單易懂了描焰∠狈瘢可以說栅螟,這個生命周期的功能實際上就是將傳入的props映射到state上面。由于16.4的修改篱竭,這個函數(shù)會在每次re-rendering之前被調(diào)用力图,這意味著什么呢?意味著即使你的props沒有任何變化掺逼,而是父state發(fā)生了變化吃媒,導致子組件發(fā)生了re-render,這個生命周期函數(shù)依然會被調(diào)用吕喘∽改牵看似一個非常小的修改,卻可能會導致很多隱含的問題氯质。
使用
這個生命周期函數(shù)是為了替代componentWillReceiveProps存在的募舟,所以在你需要使用componentWillReceiveProps的時候,就可以考慮使用getDerivedStateFromProps來進行替代了闻察。
兩者的參數(shù)是不相同的拱礁,而getDerivedStateFromProps是一個靜態(tài)函數(shù),也就是這個函數(shù)不能通過this訪問到class的屬性辕漂,也并不推薦直接訪問屬性呢灶。而是應(yīng)該通過參數(shù)提供的nextProps以及prevState來進行判斷,根據(jù)新傳入的props來映射到state钮热。
需要注意的是填抬,如果props傳入的內(nèi)容不需要影響到你的state,那么就需要返回一個null隧期,這個返回值是必須的飒责,所以盡量將其寫到函數(shù)的末尾。
// 在getDerivedStateFromProps中進行state的改變
static getDerivedStateFromProps({ error, value }, state) {
/* Keep last received error in state */
if (error && error !== state.error) {
return { error };
}
if (value && value !== state.text) {
return { text: value }
}
return null;
}
Case1 -- 多來源的不同狀態(tài)
假設(shè)我們有一個列表仆潮,這個列表受到頁面主體宏蛉,也就是根組件的驅(qū)動,也受到其本身數(shù)據(jù)加載的驅(qū)動性置。
因為這個頁面在開始渲染的時候拾并,所有的數(shù)據(jù)請求可能是通過batch進行的,所以要在根組件進行統(tǒng)一處理鹏浅,而其列表的分頁操作嗅义,則是由其本身控制。
這會出現(xiàn)什么問題呢隐砸?該列表的狀態(tài)受到兩方面的控制之碗,也就是re-render可能由props驅(qū)動,也可能由state驅(qū)動季希。這就導致了getDerivedStateFromProps會在兩種驅(qū)動狀態(tài)下被重新渲染褪那。
當這個函數(shù)被多次調(diào)用的時候幽纷,就需要注意到,state和props的變化將會怎樣影響到你的組件變化博敬。
// 組件接收一個type參數(shù)
static propTypes = {
type: PropTypes.number
}
// 組件還具有自己的狀態(tài)來渲染列表
class List extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [],
type: 0,
}
}
}
如上面代碼的例子所示友浸,組件既受控,又控制自己偏窝。當type發(fā)生變化收恢,會觸發(fā)一次getDerivedStateFromProps
,在這里更新組件的type狀態(tài)囚枪,然而派诬,在進行異步操作之后劳淆,組件又會更新list狀態(tài)链沼,這時你的getDerivedStateFromProps
函數(shù)就需要注意,不能夠僅僅判斷type是否變化來更新狀態(tài)沛鸵,因為list的變化也會更新到組件的狀態(tài)括勺。這時就必須返回一個null,否則會導致組件無法更新并且報錯曲掰。
Case2 -- 組織好你的組件
考慮一下疾捍,如果你的組件內(nèi)部既需要修改自己的type,又需要接收從外部修改的type栏妖。
是不是非陈叶梗混亂?getDerivedStateFromProps中你根本不知道該做什么吊趾。
static getDerivedStateFromProps(nextProps, prevState) {
const {type} = nextProps;
// type可能由props驅(qū)動宛裕,也可能由state驅(qū)動,這樣判斷會導致state驅(qū)動的type被回滾
if (type !== prevState.type) {
return {
type,
};
}
// 否則论泛,對于state不進行任何操作
return null;
}
如何解決這個棘手的問題呢揩尸?
好好組織你的組件,在非必須的時候屁奏,摒棄這種寫法岩榆。type要么由props驅(qū)動,要么完全由state驅(qū)動坟瓢。
如果實在沒有辦法解耦勇边,那么就需要一個hack來輔助:綁定props到state上。
constructor(props) {
super(props);
this.state = {
type: 0,
props,
}
}
static getDerivedStateFromProps(nextProps, prevState) {
const {type, props} = nextProps;
// 這段代碼可能看起來非痴哿混亂粒褒,這個props可以被當做緩存,僅用作判斷
if (type !== props.type) {
return {
type,
props: {
type,
},
};
}
// 否則崭庸,對于state不進行任何操作
return null;
}
上面的代碼可以保證在進行多數(shù)據(jù)源驅(qū)動的時候怀浆,狀態(tài)能夠正確改變谊囚。當然,這樣的代碼很多情況下是會影響到別人閱讀你的代碼的执赡,對于維護造成了非常大的困難镰踏。
從這個生命周期的更新來看,react更希望將受控的props
和state
進行分離沙合,就如同Redux
作者Dan Abramov在redux文檔當中寫的一樣Presentational and Container Components奠伪,將所有的組件分離稱為展示型組件和容器型組件,一個只負責接收props
來改變自己的樣式首懈,一個負責保持其整個模塊的state
绊率。這樣可以讓代碼更加清晰。但是在實際的業(yè)務(wù)邏輯中究履,我們有時很難做到這一點滤否,而且這樣可能會導致容器型組件變得非常龐大以致難以管理,如何進行取舍還是需要根據(jù)實際場景決定的最仑。
Case3 -- 異步
以前藐俺,我們可以在props發(fā)生改變的時候,在componentWillReceiveProps中進行異步操作泥彤,將props的改變驅(qū)動到state的改變欲芹。
componentWillReceiveProps(nextProps) {
if (props.type !== nextProps.type) {
// 在這里進行異步操作或者更新狀態(tài)
this.setState({
type: props.type,
});
this._doAsyncOperation();
}
}
這樣的寫法已經(jīng)使用了很久,并且并不會存在什么功能上的問題吟吝,但是將componentWillReceiveProps標記為deprecated的原因也并不是因為功能問題菱父,而是性能問題。
當外部多個屬性在很短的時間間隔之內(nèi)多次變化剑逃,就會導致componentWillReceiveProps被多次調(diào)用浙宜。這個調(diào)用并不會被合并,如果這次內(nèi)容都會觸發(fā)異步請求炕贵,那么可能會導致多個異步請求阻塞梆奈。
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.
這個生命周期函數(shù)會在每次調(diào)用render之前被觸發(fā),而讀過一點react源碼的童鞋都會了解称开,reactsetState操作是會通過transaction進行合并的亩钟,由此導致的更新過程是batch的,而react中大部分的更新過程的觸發(fā)源都是setState鳖轰,所以render觸發(fā)的頻率并不會非常頻繁(感謝 @leeenx20 的提醒清酥,這里描述進行了修改)。
在使用getDerivedStateFromProps的時候蕴侣,遇到了上面說的props在很短的時間內(nèi)多次變化焰轻,也只會觸發(fā)一次render,也就是只觸發(fā)一次getDerivedStateFromProps昆雀。這樣的優(yōu)點不言而喻辱志。
那么如何使用getDerivedStateFromProps進行異步的處理呢蝠筑?
If you need to perform a side effect (for example, data fetching or an animation) in response to a change in props, use componentDidUpdate lifecycle instead.
官方教你怎么寫代碼系列,但是其實也沒有其他可以進行異步操作的地方了揩懒。為了響應(yīng)props的變化什乙,就需要在componentDidUpdate中根據(jù)新的props和state來進行異步操作,比如從服務(wù)端拉取數(shù)據(jù)已球。
// 在getDerivedStateFromProps中進行state的改變
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.type !== prevState.type) {
return {
type: nextProps.type,
};
}
return null;
}
// 在componentDidUpdate中進行異步操作臣镣,驅(qū)動數(shù)據(jù)的變化
componentDidUpdate() {
this._loadAsyncData({...this.state});
}
getSnapshotBeforeUpdate(prevProps, prevState)
這個新更新代替componentWillUpdate。
常見的 componentWillUpdate 的用例是在組件更新前智亮,讀取當前某個 DOM 元素的狀態(tài)忆某,并在 componentDidUpdate 中進行相應(yīng)的處理。
這兩者的區(qū)別在于:
在 React 開啟異步渲染模式后阔蛉,在 render 階段讀取到的 DOM 元素狀態(tài)并不總是和 commit 階段相同弃舒,這就導致在
componentDidUpdate 中使用 componentWillUpdate 中讀取到的 DOM 元素狀態(tài)是不安全的,因為這時的值很有可能已經(jīng)失效了馍忽。
getSnapshotBeforeUpdate 會在最終的 render 之前被調(diào)用棒坏,也就是說在 getSnapshotBeforeUpdate 中讀取到的 DOM 元素狀態(tài)是可以保證與 componentDidUpdate 中一致的。
此生命周期返回的任何值都將作為參數(shù)傳遞給componentDidUpdate()遭笋。
小結(jié)
react為了防止部分開發(fā)者濫用生命周期,可謂非常盡心盡力了徒探。既然你用不好瓦呼,我就干脆不讓你用。一個靜態(tài)的生命周期函數(shù)可以讓狀態(tài)的修改更加規(guī)范和合理测暗。
React 16是最近一年多React更新最大的版本央串。推薦向下兼容的Fiber,哈哈碗啄,本人還沒仔細看质和,據(jù)說是防止了客戶端react在進行渲染的時候阻塞頁面的其他交互行為。Fiber源碼速覽