1. 程式人生 > >React 生命週期 學習

React 生命週期 學習

什麼是生命週期?

  • 從元件的角度

React特點是元件化開發,每一個元件的定義是一個類(也可以函式式定義無狀態元件,但是沒有生命週期,不討論),

  1. 在例項化這個類的過程中             (元件掛載)
  2. 或存在過程中的某些事件觸發      (元件更新)
  3. 或銷燬這個例項的                        (元件從頁面刪除)

所自動按照一定順序呼叫的一些函式 稱為生命週期函式。   即元件從出生到滅亡經歷的一些方法

  • 從js語言的角度

這些函式是定義在es6類的方法,將會被類的例項共享。即SomeClass.prototype.生命週期函式

如圖,輸出類的例項,可以在物件原型指標上找到生命週期的方法。

注意:es6 class 可以定義例項方法,可以定義原型方法,箭頭函式定義的例項方法將會變成例項屬性,兩種定義函式的方式區別:

  • 函式名=函式體的方式會成為例項屬性,作用域搜尋優於原型上的方法;
  • 函式名(){}方式定義的原型方法可以被所有例項共享,節約記憶體

生命週期執行順序

官方經典的生命週期圖已經展示了初始化、更新、銷燬分別對應生命週期函式執行順序,不再複述,下面總結一些需要注意的點。

哪些生命週期不能setState?

思考setState會發生什麼。。會執行上圖中的 shouldComponentUpdate->componentWillUpdate->render

->componentDidUpdate

因此在這四個函式裡執行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_”生命週期名稱將起作用。)

總結

生命週期函式很基礎,很重要。知道各種場景的函式呼叫順序,才能做出相應的優化,提高載入效率。