1. 程式人生 > >React:組件的生命周期

React:組件的生命周期

mil myself obj 表示 als 機會 dom set tran

在組件的整個生命周期中,隨著該組件的props或者state發生改變,其DOM表現也會有相應的變化。一個組件就是一個狀態機,對於特定地輸入,它總返回一致的輸出。

一個React組件的生命周期分為三個部分:實例化、存在期和銷毀時。

實例化

當組件在客戶端被實例化,第一次被創建時,以下方法依次被調用:

1、getDefaultProps
2、getInitialState
3、componentWillMount
4、render
5、componentDidMount

當組件在服務端被實例化,首次被創建時,以下方法依次被調用:

1、getDefaultProps
2、getInitialState
3、componentWillMount 4、render

componentDidMount 不會在服務端被渲染的過程中調用。

getDefaultProps

對於每個組件實例來講,這個方法只會調用一次,該組件類的所有後續應用,getDefaultPops 將不會再被調用,其返回的對象可以用於設置默認的 props(properties的縮寫) 值。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</
title> <style> </style> </head> <body> <script src="js/react.js"></script> <script src="js/JSXTransformer.js"></script> <script type="text/jsx"> var Hello = React.createClass({ getDefaultProps: function () { return { name:
neinei, username: jasper } }, render: function () { return ( <div>Hello,{this.props.name},git username is {this.props.username}</div> ) } }); React.render(<Hello/>, document.body); </script> </body> </html>

技術分享

也可以在掛載組件的時候設置 props:

var data = [{title: ‘Hello‘}];
<Hello data={data} />

    var Hello = React.createClass({
        render: function () {
            return (
                    <div>title is:{this.props.title}</div>
            )
        }
    });
    var data = [{title: ‘Hello‘}];
    React.render(<Hello title={data}/>, document.body);

或者調用 setProps (一般不需要調用)來設置其 props:

var data = [{title: ‘Hello‘}];
var Hello = React.render(<Demo />, document.body);
Hello.setProps({data:data});

    var Hello = React.createClass({
        render: function () {
            return (
                    <div>title is:{this.props.data}</div>
            )
        }
    });


    var data = [{title: ‘Hello‘}];
    var Hello = React.render(<Hello />, document.body);
    Hello.setProps({data:data});

但只能在子組件或組件樹上調用 setProps。別調用 this.setProps 或者 直接修改 this.props。將其當做只讀數據。

React通過 propTypes 提供了一種驗證 props 的方式,propTypes 是一個配置對象,用於定義屬性類型:

var survey = React.createClass({
    propTypes: {
        survey: React.PropTypes.shape({
            id: React.PropTypes.number.isRequired
        }).isRequired,
        onClick: React.PropTypes.func,
        name: React.PropTypes.string,
        score: React.PropTypes.array
        ...
    },
    
    //...
})

組件初始化時,如果傳遞的屬性和 propTypes 不匹配,則會打印一個 console.warn 日誌。如果是可選配置,可以去掉.isRequired。

getInitialState

對於組件的每個實例來說,這個方法的調用有且只有一次,用來初始化每個實例的 state,在這個方法裏,可以訪問組件的 props。每一個React組件都有自己的 state,其與 props 的區別在於 state只存在組件的內部,props 在所有實例中共享。

getInitialState 和 getDefaultPops 的調用是有區別的,getDefaultPops 是對於組件類來說只調用一次,後續該類的應用都不會被調用,而 getInitialState 是對於每個組件實例來講都會調用,並且只調一次。

    var LikeButton = React.createClass({
        getInitialState: function () {
            return {liked: false};
        },
        handleClick: function (event) {
            this.setState({liked: !this.state.liked});
        },
        render: function () {
            var text = this.state.liked ? ‘like‘ : ‘havent liked‘;
            return (
                    <p onClick={this.handleClick}>
                        You {text} this. Click to toggle.
                    </p>
            );
        }
    });

    React.render(
            <LikeButton/>,
        document.body
    );

技術分享

每次修改 state,都會重新渲染組件,實例化後通過 state 更新組件,會依次調用下列方法:

1、shouldComponentUpdate
2、componentWillUpdate
3、render
4、componentDidUpdate

但是不要直接修改 this.state,要通過 this.setState 方法來修改。

componentWillMount

該方法在首次渲染之前調用,也是再 render 方法調用之前修改 state 的最後一次機會。

render

該方法會創建一個虛擬DOM,用來表示組件的輸出。對於一個組件來講,render方法是唯一一個必需的方法。render方法需要滿足下面幾點:

  1. 只能通過 this.props 和 this.state 訪問數據(不能修改)

  2. 可以返回 null,false 或者任何React組件

  3. 只能出現一個頂級組件,不能返回一組元素

  4. 不能改變組件的狀態

  5. 不能修改DOM的輸出

render方法返回的結果並不是真正的DOM元素,而是一個虛擬的表現,類似於一個DOM tree的結構的對象。react之所以效率高,就是這個原因。

componentDidMount

該方法不會在服務端被渲染的過程中調用。該方法被調用時,已經渲染出真實的 DOM,可以再該方法中通過 this.getDOMNode() 訪問到真實的 DOM(推薦使用 ReactDOM.findDOMNode())。

    var MessageBox = React.createClass({
        componentDidMount: function () {
            console.log(‘componentDidMount‘);
        },
        killMySelf: function () {
            React.unmountComponentAtNode(document.getElementById(‘app‘))
        },
        render: function () {
            console.log(‘render‘);
            return (
                    <div>
                        <button onClick={this.killMySelf}>卸載掉這個組件</button>
                    </div>
            )
        }
    });

    var messageBox = React.render(<MessageBox/>,
        document.getElementById(‘app‘)
    )

技術分享

由於組件並不是真實的 DOM 節點,而是存在於內存之中的一種數據結構,叫做虛擬 DOM (virtual DOM)。只有當它插入文檔以後,才會變成真實的 DOM 。有時需要從組件獲取真實 DOM 的節點,這時就要用到 ref 屬性:

    var Area = React.createClass({
        render: function () {
//            this.getDOMNode(); //render調用時,組件未掛載,這裏將報錯
            return (
                <h1 ref="mainCanvas">根據ref訪問</h1>
            )
        },
        componentDidMount: function () {
            var canvas = this.refs.mainCanvas.getDOMNode();
            console.log(canvas);
            //這是有效的,可以訪問到 Canvas 節點

        }
    });
    React.render(<Area/>,
        document.getElementById(‘app‘)
    );

技術分享

需要註意的是,由於 this.refs.[refName] 屬性獲取的是真實 DOM ,所以必須等到虛擬 DOM 插入文檔以後,才能使用這個屬性,否則會報錯。

存在期

此時組件已經渲染好並且用戶可以與它進行交互,比如鼠標點擊,手指點按,或者其它的一些事件,導致應用狀態的改變,你將會看到下面的方法依次被調用

1、componentWillReceiveProps
2、shouldComponentUpdate
3、componentWillUpdate
4、render
5、componentDidUpdate

componentWillReceiveProps

組件的 props 屬性可以通過父組件來更改,這時,componentWillReceiveProps 將來被調用。可以在這個方法裏更新 state,以觸發 render 方法重新渲染組件。

    var MessageBox = React.createClass({
        getInitialState: function () {
            console.log(‘getInitialState‘);
            return {
                count: 0,
            }
        },
        render: function () {
            console.log(‘render‘);
            return (
                    <div>
                        <h1>計數:{this.state.count}</h1>
                        <Submessage message="sub message"/>
                    </div>
            )
        }
    });
    var Submessage = React.createClass({
        componentWillReceiveProps: function (nextProps) {
            console.log(‘子組件將要獲取props‘);
            console.log(nextProps);
        },
        render: function () {
            return (
                    <div>
                        <h3>寫代碼很有意思</h3>
                        <span>傳過來的props的值為{this.props.message}</span>
                    </div>
            )
        }
    });
    var messageBox = React.render(<MessageBox/>,
        document.getElementById(‘app‘)
    )

技術分享

shouldComponentUpdate

如果你確定組件的 props 或者 state 的改變不需要重新渲染,可以通過在這個方法裏通過返回 false 來阻止組件的重新渲染,返回 `false 則不會執行 render 以及後面的 componentWillUpdate,componentDidUpdate 方法。

該方法是非必須的,並且大多數情況下沒有在開發中使用。

    var MessageBox = React.createClass({
        getInitialState: function () {
            console.log(‘getInitialState‘);
            return {
                count: 0,
            }
        },
        shouldComponentUpdate: function (nextProp, nextState) {
            console.log(‘shouldComponentUpdate‘);
            console.log(nextProp)
            console.log(nextState)
            return true;
        },
        doUpdate: function () {
            this.setState({
                count: this.state.count + 1
            })
        },
        render: function () {
            console.log(‘render‘);
            return (
                    <div>
                        <h1>計數:{this.state.count}</h1>
                        <button onClick={this.doUpdate}>手動更新此組件(包括子組件)</button>
                    </div>
            )
        }
    });
    var messageBox = React.render(<MessageBox/>,
        document.getElementById(‘app‘)
    )

技術分享技術分享

componentWillUpdate

這個方法和 componentWillMount 類似,在組件接收到了新的 props 或者 state 即將進行重新渲染前,componentWillUpdate(object nextProps, object nextState) 會被調用,註意不要在此方面裏再去更新 props 或者 state。

componentDidUpdate

這個方法和 componentDidMount 類似,在組件重新被渲染之後,componentDidUpdate(object prevProps, object prevState) 會被調用。可以在這裏訪問並修改 DOM。

銷毀時

componentWillUnmount

每當React使用完一個組件,這個組件必須從 DOM 中卸載後被銷毀,此時 componentWillUnmout 會被執行,完成所有的清理和銷毀工作,在 componentDidMount 中添加的任務都需要再該方法中撤銷,如創建的定時器或事件監聽器。

當再次裝載組件時,以下方法會被依次調用:

1、getInitialState
2、componentWillMount
3、render
4、componentDidMount

反模式

在 getInitialState 方法中,嘗試通過 this.props 來創建 state 的做法是一種反模式。

//反模式
getDefaultProps: function(){
    return {
        data: new Date()
    }
},
getInitialState: function(){
    return {
        day: this.props.date - new Date()
    }
},
render: function(){
    return <div>Day:{this.state.day}</div>
}

經過計算後的值不應該賦給 state,正確的模式應該是在渲染時計算這些值。這樣保證了計算後的值永遠不會與派生出它的 props 值不同步。

//正確模式
getDefaultProps: function(){
    return {
        data: new Date()
    }
},
render: function(){
    var day = this.props.date - new Date();
    return <div>Day:{day}</div>
}

如果只是簡單的初始化 state,那麽應用反模式是沒有問題的。

總結

以下面的一張圖總結組件的生命周期:

技術分享

綜合生命周期的總例:

    var MessageBox = React.createClass({
        getInitialState: function () {
            console.log(‘getInitialState‘);
            return {
                count: 0,
            }
        },
        getDefaultProps: function () {
            console.log(‘getDefaultProps‘);
        },
        componentWillMount: function () {
            console.log(‘componentWillMount‘);
        },
        componentDidMount: function () {
            console.log(‘componentDidMount‘);
        },
        componentWillUnmount: function () {
           alert(‘componentWillUnmount‘);
        },
        shouldComponentUpdate: function (nextProp, nextState) {
            console.log(‘shouldComponentUpdate‘);
            console.log(nextProp)
            console.log(nextState)
            return true;
        },
        componentDidUpdate: function (nextProp, nextState) {
            console.log(‘componentDidUpdate‘);
            console.log(nextProp)
            console.log(nextState)
        },
        componentWillUpdate: function (nextProp, nextState) {
            console.log(‘componentWillUpdate‘);
            console.log(nextProp)
            console.log(nextState)
        },
        killMySelf: function () {
            React.unmountComponentAtNode(document.getElementById(‘app‘))
        },
        doUpdate: function () {
            this.setState({
                count: this.state.count + 1
            })
        },
        render: function () {
            console.log(‘render‘);
            return (
                    <div>
                        <h1>計數:{this.state.count}</h1>
                        <button onClick={this.killMySelf}>卸載掉這個組件</button>
                        <button onClick={this.doUpdate}>手動更新此組件(包括子組件)</button>
                        <Submessage message={‘當前計數量:‘ + this.state.count}/>
                    </div>
            )
        }
    });
    var Submessage = React.createClass({
        componentWillReceiveProps: function (nextProps) {
            console.log(‘子組件將要獲取props‘);
            console.log(nextProps);
        },
        render: function () {
            return (
                    <div>
                        <h3>寫代碼很有意思</h3>
                        <span>{this.props.message}</span>
                    </div>
            )
        }
    });
    var messageBox = React.render(<MessageBox/>,
        document.getElementById(‘app‘)
    )

技術分享技術分享

React:組件的生命周期