componentWillMount
、
componentWillReceiveProps
、
componentWillUpdate
三個生命週期函數都有eslint報警,讓咱們使用
UNSAFE_
前綴的新的生命週期函數。不由有疑問「react這是意欲何爲啊」?爲何要加
UNSAFE_
前綴?
一、componentWillMountreact
componentWillMount
生命週期發生在首次渲染前,通常使用的小夥伴大多在這裏初始化數據或異步獲取外部數據賦值。初始化數據,react官方建議放在constructor
裏面。而異步獲取外部數據,渲染並不會等待數據返回後再去渲染。瀏覽器
案例一:以下是安裝時監聽外部事件調度程序的組件示例安全
class Example extends React.Component {
state = {
value: ''
};
componentWillMount() {
this.setState({
value: this.props.source.value
});
this.props.source.subscribe(this.handleChange);
}
componentWillUnmount() {
this.props.source.unsubscribe(this.handleChange );
}
handleChange = source => {
this.setState({
value: source.value
});
};
}
複製代碼
試想一下,假如組件在第一次渲染的時候被中斷,因爲組件沒有完成渲染,因此並不會執行componentWillUnmount
生命週期(注:不少人常常認爲componentWillMount和componentWillUnmount老是配對,但這並非必定的。只有調用componentDidMount後,React才能保證稍後調用componentWillUnmount進行清理
)。所以handleSubscriptionChange
仍是會在數據返回成功後被執行,這時候setState
因爲組件已經被移除,就會致使內存泄漏。因此建議把異步獲取外部數據寫在componentDidMount
生命週期裏,這樣就能保證componentWillUnmount
生命週期會在組件移除的時候被執行,避免內存泄漏的風險。bash
如今,小夥伴清楚爲何了要用UNSAFE_componentWillMount
替換componentWillMount
了吧(注意:這裏的UNSAFE並非指安全性,而是表示使用這些生命週期的代碼將更有可能在將來的React版本中存在缺陷,特別是一旦啓用了異步渲染
)異步
二、componentWillReceivePropsasync
componentWillReceiveProps
生命週期是在props更新時觸發。通常用於props
參數更新時同步更新state參數。但若是在componentWillReceiveProps
生命週期直接調用父組件的某些有調用setState
的函數,會致使程序死循環。函數
案例二:以下是子組件componentWillReceiveProps
裏調用父組件改變state的函數示例ui
...
class Parent extends React.Component{
constructor(){
super();
this.state={
list: [],
selectedData: {}
};
}
changeSelectData = selectedData => {
this.setState({
selectedData
});
}
render(){
return (
<Clild list={this.state.list} changeSelectData={this.changeSelectData}/>
);
}
}
...
class Child extends React.Component{
constructor(){
super();
this.state={
list: []
};
}
componentWillReceiveProps(nextProps){
this.setState({
list: nextProps.list
})
nextProps.changeSelectData(nextProps.list[0]); //默認選擇第一個
}
...
}
複製代碼
如上代碼,在Child
組件的componentWillReceiveProps
裏直接調用Parent
組件的changeSelectData
去更新Parent
組件state
的selectedData
值。會觸發Parent
組件從新渲染,而Parent
組件從新渲染會觸發Child
組件的componentWillReceiveProps
生命週期函數執行。如此就會陷入死循環。致使程序崩潰。this
因此,React官方把componentWillReceiveProps
替換爲UNSAFE_componentWillReceiveProps
,讓小夥伴在使用這個生命週期的時候注意它會有缺陷,要注意避免,好比上面例子,Child
在componentWillReceiveProps
調用changeSelectData
時先判斷list
是否有更新再肯定是否要調用,就能夠避免死循環。spa
三、componentWillUpdate
componentWillUpdate
生命週期在視圖更新前觸發。通常用於視圖更新前保存一些數據方便視圖更新完成後賦值。 案例三:以下是列表加載更新後回到當前滾動條位置的案例
class ScrollingList extends React.Component {
listRef = null;
previousScrollOffset = null;
componentWillUpdate(nextProps, nextState) {
if (this.props.list.length < nextProps.list.length) {
this.previousScrollOffset = this.listRef.scrollHeight - this.listRef.scrollTop;
}
}
componentDidUpdate(prevProps, prevState) {
if (this.previousScrollOffset !== null) {
this.listRef.scrollTop = this.listRef.scrollHeight - this.previousScrollOffset;
this.previousScrollOffset = null;
}
}
render() {
return (
`<div>` {/* ...contents... */}`</div>`
);
}
setListRef = ref => { this.listRef = ref; };
}
複製代碼
因爲componentWillUpdate
和componentDidUpdate
這兩個生命週期函數有必定的時間差(componentWillUpdate
後通過渲染、計算、再更新DOM
元素,最後才調用componentDidUpdate
),若是這個時間段內用戶恰好拉伸了瀏覽器高度,那componentWillUpdate
計算的previousScrollOffset
就不許確了。若是在componentWillUpdate
進行setState
操做,會出現屢次調用只更新一次的問題,把setState放
在componentDidUpdate
,能保證每次更新只調用一次。
因此,react官方建議把componentWillUpdate
替換爲UNSAFE_componentWillUpdate
。若是真的有以上案例的需求,可使用16.3新加入的一個周期函數getSnapshotBeforeUpdate
。下面會有具體說明,這裏暫時賣個關子。
一、getDerivedStateFromProps
getDerivedStateFromProps
是官方在16.3新加入的生命週期函數,props
變化時被調用,如果父組件從新渲染,也會被調用。它返回新的props
值。
案例四:以下是getDerivedStateFromProps
的使用實例
class Example extends React.Component {
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.name !== prevState.name) {
return {
name: nextProps.name
}
}
}
}
複製代碼
能夠看到,getDerivedStateFromProps
接收最新的Props
值nextProps
、上一個state
值prevState
兩個參數,返回返回一個對象來更新state
,或者返回null
表示不須要更新state
。要注意的是,getDerivedStateFromProps
不能訪問this
,因此若是要跟上一個props
值做比較,只能是把上一個props
值存到state
裏做爲鏡像。到這裏你必定有疑問,爲何不把上一個props
值傳給getDerivedStateFromProps
?官方給的解析以下:
在第一次調用getDerivedStateFromProps
(實例化後)時,prevProps
參數將爲null
,須要在訪問prevProps
時添加if-not-null
檢查。
沒有將之前的props
傳遞給這個函數,在將來版本的React中釋放內存的一個步驟。 (若是React不須要將先前的道具傳遞給生命週期,那麼它不須要將先前的道具對象保留在內存中。)
綜上可知,getDerivedStateFromProps
正是官方新加入的用以替代componentWillReceiveProps
的方案。若是說,你的項目會考慮日後的版本兼容,建議改用getDerivedStateFromProps
。
二、getSnapshotBeforeUpdate
getSnapshotBeforeUpdate
是跟getDerivedStateFromProps
一塊兒,在16.3新加入的生命週期函數。觸發的時機在最近的更改被提交到DOM
元素前,使得組件能夠在更改以前得到當前值,今生命週期返回的任意值都會做爲第三個參數傳給componentDidUpdate
。通常當咱們須要在更新DOM
前須要保存DOM
當前的狀態時會使用這個生命週期,比較常見是用於DOM
更新前獲取滾動位置,更新後恢復到該滾動位置。好比上面的案例三,componentWillUpdate
更好的替換方案就是getSnapshotBeforeUpdate
,getSnapshotBeforeUpdate
到componentDidUpdate
只通過了更新DOM
這一操做。
案例五:以下爲案例三的更好的替換方案
class ScrollingList extends React.Component {
listRef = null;
getSnapshotBeforeUpdate(prevProps, prevState) {
if (prevProps.list.length < this.props.list.length) {
return this.listRef.scrollHeight - this.listRef.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
if (snapshot !== null) {
this.listRef.scrollTop = this.listRef.scrollHeight - snapshot;
}
}
render() {
return (
`<div>` {/* ...contents... */}`</div>`
);
}
setListRef = ref => { this.listRef = ref; };
}
複製代碼
最後,關於componentWillMount
的替換方案,官方建議把該生命週期函數的邏輯處理放到componentDidMount
裏面去。
React官網上的計劃是:
16.3:爲不安全生命週期引入別名UNSAFE_componentWillMount
,UNSAFE_componentWillReceiveProps
和UNSAFE_componentWillUpdate
。 (舊的生命週期名稱和新的別名均可以在此版本中使用。)
將來的16.x版本:爲componentWillMount
,componentWillReceiveProps
和componentWillUpdate
啓用棄用警告。 (舊的生命週期名稱和新的別名均可以在此版本中使用,但舊名稱會記錄DEV模式警告。)
17.0:刪除componentWillMount
,componentWillReceiveProps
和componentWillUpdate
。 (從如今開始,只有新的「UNSAFE_」生命週期名稱將起做用。)
其實,說了這麼多。就兩點:
一、React意識到componentWillMount
、componentWillReceiveProps
和componentWillUpdate
這三個生命週期函數有缺陷,比較容易致使崩潰。可是因爲舊的項目已經在用以及有些老開發者習慣用這些生命週期函數,因而經過給它加UNSAFE_
來提醒用它的人要注意它們的缺陷。
二、React加入了兩個新的生命週期函數getSnapshotBeforeUpdate
和getDerivedStateFromProps
,目的爲了即便不使用這三個生命週期函數,也能實現只有這三個生命週期能實現的功能。
ps:本文部份內容借鑑參考文章ReactV16.3即將更改的生命週期