React 生命週期(v16.0前 和 v16.4)

什麼是生命週期函數?
就是不用本身手動的去調用,框架會在合適的時間自動的調用該函數react

React v16.0前 生命週期

生命週期函數分類:git

圖片加載失敗!

生命週期函數執行流程:
算法

圖片加載失敗!

初始化階段

constructor()

constructor是ES6對類的默認方法,經過 new 命令生成對象實例時自動調用該方法(調用一次)。
而且,該方法是類中必須有的,若是沒有顯示定義,則會默認添加空的constructor()方法。當存在constructor的時候必須手動調用super方法。
constructor中若是要訪問this.props,則須要傳入propsbash

class Counter extends React.component{
    constructor(props){
        super(props); // 聲明constructor時必須調用super方法
        console.log(this.props); // 能夠正常訪問this.props
    }
}
複製代碼

constructor 經常使用來初始化state框架

constructor(props){
	super(props)
	this.state = {number:0} // 初始化的狀態
}
複製代碼

在類組件中也能夠經過靜態方法設置屬性dom

class Counter extends React.component{
	static defaultProps = {name:"Fan"} // 默認屬性
    constructor(props){
        super(props); // 聲明constructor時必須調用super方法
        console.log(this.props); // 能夠正常訪問this.props
    }
}
複製代碼

掛載階段

componentWillMount()

在組件掛載以前調用,且全局只調用一次。異步

若是在這個鉤子裏調用this.setState不會引發組件從新渲染,也能夠把寫在這裏的內容寫道到constructor()中,因此不多使用。ide

render()

render是一個React組件必須定義的生命週期函數,用來渲染DOM。函數

並必須 return 一個React元素(描述組件,即UI),不負責組件實際渲染工做,以後由React自身根據此元素去渲染出頁面DOM。
render必須是純函數(Pure function:函數的返回結果只依賴於它的參數;函數執行過程裏面沒有反作用)性能

不要在render裏面修改state,會觸發死循環致使棧溢出。

componentDidMount()

在組件掛載完成後調用,且全局只調用一次。
能夠在這裏使用refs,獲取真實dom元素。

該鉤子內也能夠發起異步請求,並在異步請求中能夠進行setState

更新階段

在講述此階段前須要先明確下react組件更新機制。  

setState引發的state更新或父組件從新render引發的props更新,更新後的stateprops相對以前不管是否有變化,都將引發子組件的從新render

componentWillReceiveProps(nextProps )

props發生變化以及父組件從新渲染時都會觸發該生命週期函數

在該鉤子內能夠經過參數nextProps獲取變化後的props參數,
經過this.props訪問以前的props。該生命週期內能夠進行setState

shouldComponentUpdate(nextProps,nextState)

組件掛載以後,每次調用setState後都會調用shouldComponentUpdate判斷是否須要從新渲染組件。就是經過比較nextPropsnextState及當前組件的this.propsthis.state的狀態用來判斷是否須要從新渲染

默認返回true,須要從新render,返回false則不觸發渲染。

通常咱們經過該函數來優化性能
例如:一個React項目須要更新一個小組件時,極可能須要父組件更新本身的狀態。而一個父組件的從新更新會形成它其下全部的子組件從新執行render()方法,造成新的虛擬DOM,再用diff算法對新舊虛擬DOM進行結構和屬性的比較,決定組件是否須要從新渲染
無疑這樣的操做會形成不少的性能浪費,因此咱們開發者能夠根據項目的業務邏輯,在shouldComponentUpdate()中加入條件判斷,從而優化性能

componentWillUpdate(nextProps,nextState)

組件即將被更新時觸發

shouldComponentUpdate返回true或者調用forceUpdate以後,componentWillUpdate會被調用。

不能在該鉤子中setState,不然會觸發重複循環。

componentDidUpdate(prevProps, prevState)

此方法在組件更新後被調用(除了首次render以後調用componentDidMount,其它render結束以後都是調用componentDidUpdate)

能夠操做組件更新的DOM,prevPropsprevState這兩個參數指的是組件更新前的propsstate

該鉤子內setState有可能會觸發重複渲染,須要自行判斷,不然會進入死循環。

卸載階段

componentWillUnmount()

此方法在組件被卸載前調用

能夠在這裏執行一些清理工做,好比清除組件中使用的定時器、取消Redux的訂閱事件、清除componentDidMount中手動建立的DOM元素等等,以免引發內存泄漏。

測試代碼

import React, { Component } from 'react';

class Father extends React.Component{
    static defaultProps = {name:"Fan"} // 默認屬性
    constructor(props){
        super(props)
        this.state = {number:0} 
    }
    UNSAFE_componentWillMount(){
        console.log("Father: componentWillMount") // 1
    }
    shouldComponentUpdate(nextProps,nextState){
        console.log("Father: shouldComponentUpdate") // 
        // if(nextState.number%2 === 0){
        //     return true
        // }else{
        //     return false;
        // }
        return true;
    }
    componentWillUpdate(){
        console.log("Father: componentWillUpdate") // 
    }
    componentDidUpdate(){
        console.log("Father: componentDidUpdate") 
    }
    handleClick = ()=>{
        this.setState({number:this.state.number+1})
    }
    render(){
        console.log("Father: render")  // 2
        return(
            <div>
                <h1>父組件</h1>
                <p>{this.state.number}</p>
                <button onClick={this.handleClick}>+</button>
                <hr/>
                {this.state.number%2==0 ? <Son number={this.state.number}></Son> : null}
            </div>
        )
    }
    componentDidMount(){
        console.log("Father: componentDidMount") // 3
    }
}

class Son extends React.Component{
    UNSAFE_componentWillMount(){
        console.log("Son: componentWillMount") // 1
    }
    componentDidMount(){
        console.log("Son: componentDidMount") // 3
    }
    componentWillReceiveProps(){
        console.log("Son: componentWillReceiveProps")
    }
    shouldComponentUpdate(){
        console.log("Son: shouldComponentUpdate")
        // return false;
        return true;
    }
    componentWillUpdate(){
        console.log("Son: componentWillUpdate") // 
    }
    componentDidUpdate(){
        console.log("Son: componentDidUpdate") 
    }
    render(){
        console.log("Son: render")
        return(
            <div>
                <h1>子組件</h1>
                <p>{this.props.number}</p>
            </div>
        )
    }
    componentWillUnmount(){
        console.log("Son: componentWillUnmount") 
    }
}
export default Father;
複製代碼

運行結果(須要下去本身慢慢分析):

圖片加載失敗!

React v16.4 生命週期

圖片加載失敗!

移除的生命週期函數:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

雖然廢棄了這三個生命週期方法,可是爲了向下兼容,將會作漸進式調整。
在16.3 版本並未刪除這三個生命週期,同時還爲它們新增以 UNSAFE_ 前綴爲別名的三個函數 UNSAFE_componentWillMount()UNSAFE_componentWillReceiveProps()UNSAFE_componentWillUpdate()
在 16.4 版本給出警告將會棄用 componentWillMount()componentWillReceiveProps()componentWillUpdate() 三個函數
而後在 17.0 版本將會刪除 componentWillMount()componentWillReceiveProps()componentWillUpdate() 這三個函數,會保留使用 UNSAFE_componentWillMount()UNSAFE_componentWillReceiveProps()UNSAFE_componentWillUpdate()

新增的聲明周期函數:

  • static getDerivedStateFromProps(nextProps, prevState)
  • getSnapshotBeforeUpdate(prevProps, prevState)

爲何改變生命週期?

從上面的生命週期的圖中能夠看出,被廢棄的三個函數都是在render以前,由於fiber的出現,極可能由於高優先級任務的出現而打斷現有任務致使它們會被執行屢次,這確定不是你想要的結果。

另外的一個緣由則是,React想約束使用者,好的框架可以讓人不得已寫出容易維護和擴展的代碼。

有篇文章感受寫的很好新老生命週期對比

static getDerivedStateFromProps(nextProps, prevState)

在每次渲染以前都會調用,無論形成從新渲染的緣由是什麼,無論初始掛載仍是後面的更新都會調用,這一點和UNSAFE_componentWillReceiveProps不一樣(只有當父組件形成從新渲染時才調用)

該方法傳入的兩個參數:nextProps表示父組件傳入的值,prevState表示組件自身的state

使用該方法,須要在該方法中返回一個對象或null:若是返回的是對象,則會更新 state,若是返回的是null,則表示不更新。

使用該方法的時候須要初始化state,不然在控制檯中會出現警告信息,不能在該方法內部,調用this.state

getSnapshotBeforeUpdate(prevProps, prevState)

這個方法在 render() 以後,componentDidUpdate() 以前調用。

該方法傳入的兩個參數:prevProps表示更新前的 props,prevState表示更新前的 state

返回值稱爲一個快照(snapshot),若是不須要 snapshot,則必須顯示的返回 null —— 由於返回值將做爲 componentDidUpdate() 的第三個參數使用。因此這個函數必需要配合 componentDidUpdate() 一塊兒使用

這個函數的做用是在真實 DOM 更新(componentDidUpdate)前,獲取一些須要的信息(相似快照功能),而後做爲參數傳給 componentDidUpdate。例如:在getSnapShotBeforeUpdate中獲取滾動位置,而後做爲參數傳給 componentDidUpdate,就能夠直接在渲染真實的 DOM 時就滾動到須要的位置。

componentDidCatch(err,info)

在上面沒說,這裏說一下

任何子組件在渲染期間,生命週期方法中或者構造函數 constructor 發生錯誤時調用。

該方法傳入的兩個參數:err表示拋出的錯誤,info表示帶有componentStack key的對象,其中包含有關組件引起錯誤的棧信息。

錯誤邊界不會捕獲下面的錯誤:

  • 事件處理 (Event handlers) (由於事件處理不發生在 React 渲染時,報錯不影響渲染)
  • 異步代碼 (Asynchronous code) (例如:setTimeout or requestAnimationFrame callbacks)
  • 服務端渲染 (Server side rendering)
  • 錯誤邊界自己(而不是子組件)拋出的錯誤

測試代碼

import React, { Component } from 'react';

class Father extends Component{
    constructor(props){
        super(props)
        this.state = {number:0} 
    }
    handleClick = ()=>{
        this.setState({number:this.state.number+1})
    }
    render(){
        return(
            <div>
                <h1>父組件</h1>
                <p>{this.state.number}</p>
                <button onClick={this.handleClick}>+</button>
                <hr/>
                <Son number={this.state.number}></Son>
            </div>
        )
    }
}
class Son extends React.Component{
    state = {
        number:0
    }
    // nextProps,prevState
    // nextProps 就是父給子傳遞的過來的新的數據 
    // prevState 是子的上一次的狀態
    //每次更新都會觸發
    static getDerivedStateFromProps(nextProps,prevState){
        // nextProps 表示父向下傳遞的新的值 1 2 3 4 5
        // prevState  0 0 0 
        console.log(nextProps,prevState)  // {number: 0}  {number: 0}
        let {number} = nextProps; 
        // console.log(number)
        // if(number%2 === 0){
        //     return { number:number+10 }
        // }else{
        //     return { number:number+100 }
        // }

        // prevState 表示是子組件的上一次狀態
        if(number%2 === 0){
            console.log(prevState.number)
            return { number:prevState.number + number+10 }
        }else{
            console.log(prevState.number)
            return { number:prevState.number + number+100 }
        }
    }
    handleChange = ()=>{
        this.setState({
            number:this.state.number+1000
        })
    }
    render(){
        return(
            <div>
                <h1>子組件</h1>
                <p>{this.state.number}</p>
                <button onClick={this.handleChange}>改變狀態</button>
            </div>
        )
    }
}

export default Father;
複製代碼

運行效果:

圖片加載失敗!

import React, { Component } from 'react';

class News extends Component {
    constructor(props) {
        super(props)
        this.scrollRef = React.createRef();
    }
    state = {
        // news:["news6","news5","new4","news3","news2","new1"]
        news: []
    }
    componentDidMount() {
        this.timer = setInterval(() => {
            this.setState({
                news: [`${this.state.news.length}`, ...this.state.news]
            })
        }, 1000)
    }
    componentWillUnmount() {
        clearInterval(this.timer)
    }
    // 獲取更新以前dom的快照
    getSnapshotBeforeUpdate() {
        // console.log(this.scrollRef.current.scrollHeight)
        // 在getSnapshotBeforeUpdate中返回了一個值,這個值會給componedDidUpdate的最後一個參數
        return this.scrollRef.current.scrollHeight;
    }
    componentDidUpdate(prevProps, prevState, lastScrollHeight) {
        console.log(lastScrollHeight)
        console.log(this.scrollRef.current.scrollTop)  // 0
        let scrollTop = this.scrollRef.current.scrollTop;
        this.scrollRef.current.scrollTop = scrollTop + (this.scrollRef.current.scrollHeight - lastScrollHeight)
    }
    render() {
        let styles = {
            height: "100px",
            width: "200px",
            border: "1px solid red",
            overflow: "auto"
        }
        return (
            <ul style={styles} ref={this.scrollRef}>
                {
                    this.state.news.map((item, index) => <li key={index}>{item}</li>)
                }
            </ul>
        )
    }
}

export default News;
複製代碼

運行效果:

圖片加載失敗!


QAQ

相關文章
相關標籤/搜索