React 生命週期 學習
什麼是生命週期?
- 從元件的角度
React特點是元件化開發,每一個元件的定義是一個類(也可以函式式定義無狀態元件,但是沒有生命週期,不討論),
- 在例項化這個類的過程中 (元件掛載)
- 或存在過程中的某些事件觸發 (元件更新)
- 或銷燬這個例項的 (元件從頁面刪除)
所自動按照一定順序呼叫的一些函式 稱為生命週期函式。 即元件從出生到滅亡經歷的一些方法。
- 從js語言的角度
這些函式是定義在es6類的方法,將會被類的例項共享。即SomeClass.prototype.生命週期函式
如圖,輸出類的例項,可以在物件原型指標上找到生命週期的方法。
注意:es6 class 可以定義例項方法,可以定義原型方法,箭頭函式定義的例項方法將會變成例項屬性,兩種定義函式的方式區別:
- 函式名=函式體的方式會成為例項屬性,作用域搜尋優於原型上的方法;
- 函式名(){}方式定義的原型方法可以被所有例項共享,節約記憶體
生命週期執行順序
官方經典的生命週期圖已經展示了初始化、更新、銷燬分別對應生命週期函式執行順序,不再複述,下面總結一些需要注意的點。
哪些生命週期不能setState?
思考setState會發生什麼。。會執行上圖中的 shouldComponentUpdate->componentWillUpdate->render
因此在這四個函式裡執行setState()就會進入死迴圈,導致記憶體洩漏,以下是瀏覽器報錯
componentWillMount和componentWillReceiveProps的特殊順序
正常來講,setState會觸發一套更新機制的生命週期,但如果在componentWillMount裡進行setState,並不會觸發re-render,而是會繼續render->DidMount結束;componentWillReceiveProps也是這樣,不會執行多餘的一次scu->willud->render->didud(自行理解簡寫)
父子元件的生命週期順序
demo:
import React, { Component } from 'react';
export default class AppFather extends Component {
constructor(props) {
super(props);
this.state = {
id: 'father',
show: true
}
}
componentWillUnmount(){
console.log('原型屬性','father will unmount')
}
componentDidMount=()=>{
console.log('例項方法','father did mount')
}
render() {
console.log(this)
return (
<div>
<p onClick={()=>{this.setState({show:!this.state.show})}}>重置生命週期</p>
<p onClick={()=>{this.setState({id:'father1'})}} > im father:{this.state.id} </p>
{this.state.show && <AppSon fathername={this.state.id}></AppSon>}
</div>
)
}
}
class AppSon extends Component {
constructor(props) {
super(props);
this.state={
id:'son'
}
console.log('例項',this)
// this.w=()=>{};
}
// a=()=>{}
// c=3
// jason(){
// console.log('自定義this',this)
// }
componentWillMount(){
console.log('兒子 will mount')
// this.setState({id:'ss'})
}
componentDidMount(){
console.log('兒子 did mount')
//this.setState({id:'ssdid'})
}
// getSnapshotBeforeUpdate(){
// console.log('snap')
// }
// static getDerivedStateFromProps(){
// console.log('derive')
// return null
// }
componentWillReceiveProps(){
console.log('兒子 receive props')
return false
}
shouldComponentUpdate(...rest){
// console.log(rest)
// console.log(this.state,this.props)
console.log('兒子should props')
// this.setState({id:'ssdid'})
return true
}
componentWillUpdate(){
console.log('兒子will update')
// this.setState({id:'ssdid'})
}
componentDidUpdate(){
console.log('兒子did update')
}
componentWillUnmount(){
console.log('兒子 unmount')
}
render() {
console.log('兒子 render');
return (
<div onClick={()=>{this.setState({id:'son1'})}}>im son {this.state.id}
{/* <span>{this.props.fathername}</span> */}
<span> <AppSunZi></AppSunZi></span>
</div>
)
}
}
class AppSunZi extends Component {
componentWillMount(){
console.log('孫子will mount')
// this.setState({id:'ss'})
}
componentDidMount(){
console.log('孫子did mount')
//this.setState({id:'ssdid'})
}
shouldComponentUpdate(){
console.log('sunzi should props')
return true
}
componentWillUnmount(){
console.log('sunzi will unmount')
}
render(){
console.log('sunzi render')
return <div>
sunzi
</div>
}
}
控制檯列印如下:
可以看出,父元件先進入willMount->render,子元件再willMount,當子元件渲染結束 執行子元件的didMount後,再執行父元件的didMount,更新過程同理。銷燬過程順序為:先執行父元件的willUnmount,再執行子元件的willUnmount。
何時銷燬?
JSX裡引入元件的閉合標籤,代表元件的例項化過程,通過一定邏輯不渲染這個標籤,也就是解除安裝元件的過程。
那麼如何判斷何時要解除安裝一個特定元件呢,這就涉及到react 虛擬dom的 diff演算法(可移步其他部落格,如https://segmentfault.com/a/1190000010686582)簡單來講,對於一個元件,解除安裝有兩種情況:
- 同層級下,元件名變化,之前元件是A,更新後變成B(或空),就會刪除A元件;
- 元件的Props的key是個特殊的props,演算法會根據Key來判斷是更新還是刪除還是移動,因此前後元件的Key不同,也會刪除重新渲染
示例:
{this.state.show && <AppSon fathername={this.state.id}></AppSon>}
<AppSon key={this.props.XX.id}></AppSon>
銷燬一個元件有很實用的意義,當再次建立元件時,會重新呼叫渲染時的生命週期函式,並且在之前的非同步操作可能導致的頁面變化也不會生效。
React16版本新生命週期函式
componentDidCatch
生命週期用來處理錯誤邊界,用法非常簡單,注意:只會捕捉render的錯誤
//...
componentDidCatch(error, info) {
this.setState({ error, info });
}
//...
render(){
return {this.state.error ? <Error>錯誤的顯示元件</Error> : <App>正常元件</App>}
}
getDerivedStateFromProps getSnapshotBeforeUpdate
新的靜態getDerivedStateFromProps
生命週期在元件例項化以及接收新props
後呼叫。它可以返回一個物件來更新state
,或者返回null來表示新的props
不需要任何state
更新。
新的getSnapshotBeforeUpdate
生命週期在更新之前被呼叫(例如,在DOM被更新之前)。此生命週期的返回值將作為第三個引數傳遞給componentDidUpdate
。 (這個生命週期不是經常需要的,但可以用於在恢復期間手動儲存滾動位置的情況。)
React遵循語義版本控制, 所以這種改變將是漸進的。目前的計劃是:
- 16.3:為不安全生命週期引入別名UNSAFE_componentWillMount,UNSAFE_componentWillReceiveProps和UNSAFE_componentWillUpdate。 (舊的生命週期名稱和新的別名都可以在此版本中使用。)
- 未來的16.x版本:為componentWillMount,componentWillReceiveProps和componentWillUpdate啟用棄用警告。 (舊的生命週期名稱和新的別名都可以在此版本中使用,但舊名稱會記錄DEV模式警告。)
- 17.0:刪除componentWillMount,componentWillReceiveProps和componentWillUpdate。 (從現在開始,只有新的“UNSAFE_”生命週期名稱將起作用。)
總結
生命週期函式很基礎,很重要。知道各種場景的函式呼叫順序,才能做出相應的優化,提高載入效率。