react的生命周期

生命周期是我們在react中碰到的第二個新概念。如同人有生老病死,自然界有日月更替,每個組件也有被創(chuàng)建、更新和刪除,如同有生命的機(jī)體一樣。

react嚴(yán)格定義了組件的生命周期,生命周期可能會經(jīng)歷三個過程:

  • 裝載過程(mount):React.js 將組件渲染,并且構(gòu)造 DOM 元素然后塞入頁面的過程

  • 更新過程(update):當(dāng)組件被重新渲染的過程

  • 卸載過程(unmount):組件從DOM中刪除的過程

裝載過程

裝載過程依次調(diào)用的函數(shù)如下:

constructor:初始化state,綁定成員函數(shù)的this環(huán)境

//通過React.createClass這兩個生命周期才會用到,getInitialState被constructor中的this.state代替了,getDefaultProps被static defaultProps代替了
React.createClass({
  getInitialState:初始化state
  getDefaultProps:提供默認(rèn)的props
})

componentWillMount:在render之前調(diào)用,一般不再這個函數(shù)里里面做什么事,因為這時候dom還沒有渲染出來,所有在這里可以做的事都可以在componentDidMount里面執(zhí)行??梢栽诜?wù)器端執(zhí)行,也可以在瀏覽器端執(zhí)行。在這里面執(zhí)行setState,組件會更新state,但組件只渲染一次,因此這是無意義的執(zhí)行,初始化的state都可以放到constructor里面去做

render:render函數(shù)并不做實際的渲染,它只是返回一個jsx,最終由react來操作渲染過程,它可以返回null或者false,等于告訴react這次不要渲染任何dom元素。render應(yīng)該是一個純函數(shù),完全根據(jù)props、state來決定要返回的東西。render里面不能使用setState,因為一個純函數(shù)不應(yīng)該引起狀態(tài)的改變

componentDidMount:在組件render之后執(zhí)行,所有動畫的啟動,ajax請求、定時器皆可以放到這里面執(zhí)行,這個函數(shù)也是用的最多的函數(shù)之一,這里面執(zhí)行setState當(dāng)然可以會再次更新組建。這個函數(shù)與render函數(shù)有一個不大不小的坑,可能有人認(rèn)為render執(zhí)行后立即調(diào)用componentDidMount,實際上卻不是這樣的。每一次render都是遞歸執(zhí)行的,而之所以react這么設(shè)計是因為每個render函數(shù)返回的都是jsx表示的對象,然后由react根據(jù)返回對象來決定如何渲染,而react肯定是要把所有組件的結(jié)果綜合起來,才能知道如何產(chǎn)生對應(yīng)的DOM修改。所以只有當(dāng)react調(diào)用所有組件的render之后才可能完成裝載繼而執(zhí)行componentDidMount,所以componentDidMount才連在一起被調(diào)用??梢栽跒g覽器端執(zhí)行,不能在服務(wù)器端執(zhí)行

示例

// ControlPanel
import React,{Component} from 'react'
import Counter from './Counter'

const style = {
    margin:'20px'
}
export default class ControlPanel extends Component{
    render(){
        console.log('enter ControlPanel render');
        return(
            <div style={style}>
                <Counter caption="First" />
                <Counter caption="Second" initValue={10} />
                <Counter caption="Third" initValue={20} />
                <button onClick={ () => this.forceUpdate() }>
                    Click me to re-render!
                </button>
            </div>
        )
    }
}
//Counter 
import React,{Component} from 'react'
import PropTypes from 'prop-types'

const buttonStyle = {
    margin: '10px'
}

export default class Counter extends Component{
    static propTypes = {
        caption: PropTypes.string.isRequired,
        initValue: PropTypes.number
    }

    static defaultProps = {
        initValue: 0
    }
    constructor(props){
        super(props)
        this.onClickIncrementButton = this.onClickIncrementButton.bind(this)
        this.onClickDecrementButton = this.onClickDecrementButton.bind(this)

        this.state = {
            count:props.initValue,
        }
        console.log('enter constructor: ' + props.caption);
    }

    componentWillMount() {
        console.log('enter componentWillMount ' + this.props.caption);
    }

    componentDidMount() {
        console.log('enter componentDidMount ' + this.props.caption);
    }

    componentWillReceiveProps(nextProps) {
        console.log('enter componentWillReceiveProps ' + this.props.caption)
    }

    shouldComponentUpdate(nextProps, nextState) {
        console.log('enter shouldComponentUpdate ' + this.props.caption)
        return (nextProps.caption !== this.props.caption) ||
                (nextState.count !== this.state.count);
        //return true
       
      }


    componentWillUpdate(){
        console.log('enter componentWillUpdate ' + this.props.caption)
    }

    componentDidUpdate(){
        console.log('enter componentDidUpdate ' + this.props.caption)
    }

    onClickIncrementButton() {
        this.setState({count: this.state.count + 1});
    }
    
    onClickDecrementButton() {
        this.setState({count: this.state.count - 1});
    }

    render(){
        console.log('enter render ' + this.props.caption);
        return(
            <div>
                <button style={buttonStyle} onClick={this.onClickIncrementButton}>+</button>
                <button style={buttonStyle} onClick={this.onClickDecrementButton}>-</button>
                <span>{this.props.caption} count: {this.state.count}</span>
            </div>
        )
    }
}


  
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import ControlPanel from './ControlPanel';

ReactDOM.render(<ControlPanel />, document.getElementById('root'));

結(jié)果:

enter ControlPanel render (父組件)

enter constructor first
enter componentWillMount first
enter render first

enter constructor second
enter componentWillMount second
enter render second
 
enter constructor third
enter componentWillMount third
enter render third

enter componentDidMount first
enter componentDidMount second
enter componentDidMount third

更新過程

componentWillReceiveProps:按字面意思是組件是否接受props,但是實際上只要父組件render函數(shù)被調(diào)用,在render函數(shù)里面的子組件就會經(jīng)歷更新過程,而不管父組件傳給子組件的props有沒有改變,都會觸發(fā)子組件的componentWillProps,上面那個示例只要我點擊re-render那個按鈕強(qiáng)制更新父組件,明明傳給子組件的props沒有更新,但是控制臺卻打印了enter componentWillReceiveProps 。通過this.setState觸發(fā)的更新過程不會調(diào)用這個函數(shù),這是因為這個函數(shù)適合根據(jù)新的props值(也就是參數(shù)nextProps)來計算要不要更新內(nèi)部狀態(tài)state,而更新內(nèi)部狀態(tài)的方法就是setState,如果setState的調(diào)用導(dǎo)致componentWillReceiveProps再一次被調(diào)用,就會陷入一個死循環(huán)。雖然this.setState觸發(fā)的更新過程不會調(diào)用這個函數(shù),但是可以在這個函數(shù)中執(zhí)行this.setState,雖然沒什么用。

shouldComponentUpdate:render函數(shù)決定了該渲染什么,而shouldComponentUpdate決定了一個組件什么時候不需要渲染。而它們兩個也是所有函數(shù)中唯二需要返回結(jié)果的函數(shù),render的返回結(jié)果用于構(gòu)造dom對象,而shouldComponentUpdate函數(shù)需要一個返回值,告訴react這個組件這次更新過程是否要繼續(xù)。在更新過程中,react首先調(diào)用shouldComponentUpdate,若它返回true則繼續(xù)更新,接下來調(diào)用render函數(shù),若它返回false,就立即停止更新過程。它能夠大大提高react的性能,如果確定特定組件在分析后很慢,則可以將其更改為從React.PureComponent繼承,React.PureComponent實現(xiàn)了與淺層prop和state比較的shouldComponentUpdate()。如果你確信你想用手寫它,你可以將this.props與nextProps和this.state與nextState進(jìn)行比較,并返回false來告訴React更新可以被跳過。不能在這里調(diào)用setState,否則會造成循環(huán)調(diào)用。

componentWillUpdate:在更新之前做準(zhǔn)備工作,如果我們想根據(jù)新的props值來計算要不要更新內(nèi)部狀態(tài)state,可以在componentWillReceiveProps 這個函數(shù)中來執(zhí)行。同shouldComponentUpdate,不能在這里直接執(zhí)行setState,會造成死循環(huán),不能在這里執(zhí)行直接setState,會造成死循環(huán),但是可以設(shè)置一個邊界條件,此時就可以setState了。

render:重新渲染

componentDidUpdate:可以在這里操縱dom以及進(jìn)行網(wǎng)絡(luò)請求,與
componentDidUpdate不同的是componentDidMount既可以在瀏覽器端執(zhí)行又可以在服務(wù)器端執(zhí)行。但是在服務(wù)器端只需產(chǎn)出html字符串,所以正常情況下也不會調(diào)用componentDidUpdate。值得一提的是由于render函數(shù)是遞歸調(diào)用,所以組件更新跟組件裝載一樣,我們的示例組件會先自己更新到render,最后幾個componentDidUpdate依次調(diào)用。這個方法可以調(diào)用setState。

此時先在shouldComponentUpdate返回true才能看到效果,然后點擊re-render按鈕

enter ControlPanel render

enter componentWillReceiveProps First
enter shouldComponentUpdate First
enter componentWillUpdate First
enter render First

enter componentWillReceiveProps Second
enter shouldComponentUpdate Second
enter componentWillUpdate Second
enter render Second

enter componentWillReceiveProps Third
enter shouldComponentUpdate Third
enter componentWillUpdate Third
enter render Third

enter componentDidUpdate First
enter componentDidUpdate Second
enter componentDidUpdate Third

卸載過程

componentwillUnmout:組件卸載的時候調(diào)用,這個函數(shù)適合清理性的工作,比如清除定時器、清除在componentDidMount創(chuàng)造的dom元素。

以下是從網(wǎng)上找來的圖,引用于深入react技術(shù)棧

生命周期整體流程圖
生命周期全局圖
生命周期執(zhí)行順序圖
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 即使最近還是有點拖延,可還是再看一些關(guān)于React的東西。看到React組件的生命周期的時候,讓我想到了Vue同樣...
    白霽閱讀 605評論 0 9
  • 實例化 首次實例化 getDefaultProps getInitialState componentWillMo...
    奔跑在春風(fēng)里閱讀 295評論 0 0
  • 3. JSX JSX是對JavaScript語言的一個擴(kuò)展語法, 用于生產(chǎn)React“元素”,建議在描述UI的時候...
    pixels閱讀 2,878評論 0 24
  • 掛載階段的組件生命周期 我們將reactjs組件渲染并構(gòu)造DOM元素然后塞入頁面的過程稱為組件的掛載, 我們知道在...
    秋楓殘紅閱讀 464評論 0 1
  • React 組件的一生,是光榮的一生,是革命的一生,在它的一生中會經(jīng)歷這樣幾個階段: 裝載階段 更新階段 銷毀階段...
    柏丘君閱讀 1,006評論 0 0