ReactJS入門(二)—— 組件的生命周期


如果你熟悉avalon,使用過 data-include-rendered 和 data-include-loaded 等回調方法,那么你會很好地理解React組件的各個生命周期。

說白了其實就是React組件狀態變化前后的時間點,我們可以利用生命周期的接口在相應的時間點做回調操作。

React的官方文檔提及了如下幾個組件的生命周期:

Mounting/組件掛載相關: 

componentWillMount
componentDidMount

Updating/組件更新相關:

componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate

Unmounting/組件移除相關:

componentWillUnmount 
 

下面我們將通過一些實例來理解它們。順便說下本文的示例都可以在我的github上下載到。

一. Mounting/組件掛載相關

componentWillMount

在組件掛載之前執行操作,但僅執行一次,即使多次重復渲染該組件,或者改變了組件的state:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>componentWillMount</title>
    <script src="react.js"></script>
    <script src="JSXTransformer.js"></script>
</head>
<body>
<div id="a">123</div>
<script type="text/jsx">
    var i = 0;
    var Component1 = React.createClass({
        componentWillMount: function(){
            console.log(i++)
        },
        getInitialState: function() {
            return {
                isClick: !1
            }
        },
        clickCb: function() {
            this.setState({
                isClick : !0
            })
        },
        render: function() {
            return <div onClick={this.clickCb}>isClick:{this.state.isClick? 'yes' : 'nope'}</div>
        }
    });
    var div = document.getElementById('a');
    React.render(
        <Component1 />,div
    );
    React.render(
        <Component1 />,div
    );
</script>
</body>
</html>

如果希望該回調能執行多次,可以使用 React.unmountComponentAtNode(該方法我們下篇文章再介紹)移除掉已有的組件,然后再重新 render:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>componentWillMount</title>
    <script src="react.js"></script>
    <script src="JSXTransformer.js"></script>
</head>
<body>
<div id="a">123</div>
<script type="text/jsx">
    var i = 0;
    var Component1 = React.createClass({
        componentWillMount: function(){
            console.log(i++)
        },
        getInitialState: function() {
            return {
                isClick: !1
            }
        },
        clickCb: function() {
            this.setState({
                isClick : !0
            })
        },
        render: function() {
            return <div onClick={this.clickCb}>isClick:{this.state.isClick? 'yes' : 'nope'}</div>
        }
    });
    var div = document.getElementById('a');
    React.render(
        <Component1 />,div
    );
    React.unmountComponentAtNode(div);  //移除掉已有組件
    React.render(
        <Component1 />,div
    );
</script>
</body>
</html>
View Code

可以看到輸出了兩行:

componentDidMount

顧名思義可以猜到這個是在組件初始化掛載之后執行的,比如我們可以利用它來隱藏掉頁面的loading菊花層。

同 componentWillMount 一樣,同一個組件重復渲染只執行一次,卸載組件后重新渲染可以重新觸發一次:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>componentDidMount</title>
    <script src="react.js"></script>
    <script src="JSXTransformer.js"></script>
</head>
<body>
<div id="a">123</div>
<div id="b">123</div>
<script type="text/jsx">
    var i = 0,
        div = document.getElementById('a'),
        div2 = document.getElementById('b');

    var Component1 = React.createClass({
        componentDidMount: function(){
            console.log(i++)
        },
        clickCb: function() {
            React.render(
                <Component1 />, div2
            );
        },
        render: function() {
            return <div onClick={this.clickCb}>點我給下一個div掛載組件</div>
        }
    });

    React.render(
        <Component1 />, div
    );
    //React.unmountComponentAtNode(div);  //移除掉已有組件
    React.render(
        <Component1 />, div
    );
</script>
</body>
</html>
View Code

注意上述代碼點擊div1時會將組件掛載到div2上,觸發div2的組件的 componentDidMount 回調(畢竟div1和div2上的組件並非同一個)。

二. Updating/組件更新相關:

componentWillReceiveProps

在組件接收到新props的時間點之前調用,注意組件初始化渲染時則不會執行:
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>componentWillReceiveProps</title>
    <script src="react.js"></script>
    <script src="JSXTransformer.js"></script>
</head>
<body>
<div id="a">123</div>
<div id="b">123</div>
<script type="text/jsx">
    var i = 0,
        div = document.getElementById('a'),
        div2 = document.getElementById('b');

    var Component1 = React.createClass({
        componentWillReceiveProps: function(){
            console.log(i++)
        },
        clickCb: function() {
            React.render(
                <Component1 />, div2
            );
        },
        render: function() {
            return <div onClick={this.clickCb}>點我給下一個div掛載組件</div>
        }
    });

    React.render(
        <Component1 />, div  //初始化不會觸發componentWillReceiveProps
    );
    React.render(
            <Component1 />, div   //重復渲染會觸發componentWillReceiveProps
    );
    React.unmountComponentAtNode(div);  //移除掉已有組件
    React.render(
        <Component1 />, div  //初始化不會觸發componentWillReceiveProps
    );
</script>
</body>
</html>
View Code

注意我們移除掉組件再掛載的時候,相當於重新初始化渲染了組件(得到的props是初始化props而不是新props),故不會觸發 componentWillReceiveProps 。

而當我們在div2掛載了組件后再點擊div2來重新渲染它的組件,會觸發 componentWillReceiveProps :

該接口帶有一個參數 nextProps,我們可以利用它來獲取新 props 的值(this.props 獲取到的是當前的,也就是舊的 props)

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>componentWillReceiveProps</title>
    <style>
        div:active{color:orangered;}
    </style>
    <script src="react.js"></script>
    <script src="JSXTransformer.js"></script>
</head>
<body>
<div id="a">123</div>
<script type="text/jsx">
    var i = 0,
        div = document.getElementById('a'),
        render = function(){
            React.render(
                    <Component1 i={i++} />, div
            );
        };

    var Component1 = React.createClass({
        componentWillReceiveProps: function(nextProps){
            console.log(this.props.i, nextProps.i)
        },
        render: function() {
            return <div onClick={render}>props.i的值是:{this.props.i}</div>
        }
    });
    render();
</script>
</body>
</html>
View Code

通過點擊div1的組件,可以輸出 componentWillReceiveProps 時間點(這時候還沒重新執行渲染)的 props 以及即將獲取到的新 props,執行如下:

shouldComponentUpdate

前面咱們學習的接口都是叫 componentXXX,而這個把 should 放在前面,翻譯過來其實就是“是否應該XXX”的意思,那么可以把該接口直接理解為“組件是否應該做更新”的意思,即其了一個決定組件要不要重新渲染的作用。

該接口實際是在組件接收到了新的 props 或者新的 state 的時候(該時間點render還沒執行哦)會立即調用,然后通過返回值(Boolean)來決定是否要重新渲染當前的組件。

該接口帶有兩個參數,第一個參數表示新的props,第二個參數表示新的state。

我們來個例子,比方要求div要點擊3次之后,才重新渲染自身組件:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>shouldComponentUpdate</title>
    <style>
        div:active{color:orangered;}
    </style>
    <script src="react.js"></script>
    <script src="JSXTransformer.js"></script>
</head>
<body>
<div id="a">123</div>
<script type="text/jsx">
    var div = document.getElementById('a');

    var Component1 = React.createClass({
        getInitialState: function(){
            return { i : 0 }
        },
        shouldComponentUpdate: function(nextProps, nextState){
            console.log( this.state.i, nextState.i );
            return nextState.i > 3 ? true : false; //返回true才會渲染組件
        },
        clickCb: function(){
            this.setState({
                i : this.state.i + 1
            })
        },
        render: function() {
            return <div onClick={this.clickCb}>state.i的值是:{this.state.i}</div>
        }
    });
    React.render(
            <Component1 />, div
    );
</script>
</body>
</html>
View Code

執行如下,點擊第四次之后才會渲染組件,在div里顯示出正確的新state.i:

componentWillUpdate

同 shouldComponentUpdate 一樣,在組件收到新的 props 或者 state 的時候會立即調用,而且也有着倆個參數來獲取新的 props 和 state。

不過本接口會在 shouldComponentUpdate 執行並返回了 true 的時候才會被調用。我們拿上一個代碼示例做點小修改:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>componentWillUpdate</title>
    <style>
        div:active{color:orangered;}
    </style>
    <script src="react.js"></script>
    <script src="JSXTransformer.js"></script>
</head>
<body>
<div id="a">123</div>
<script type="text/jsx">
    var div = document.getElementById('a');

    var Component1 = React.createClass({
        getInitialState: function(){
            return { i : 0 }
        },
        shouldComponentUpdate: function(nextProps, nextState){
            console.log( this.state.i, nextState.i );
            return nextState.i > 3 ? true : false; //返回true才會執行componentWillUpdate並重新渲染組件
        },
        componentWillUpdate: function(nextProps, nextState){
            console.log( 'yoyoyo', this.state.i, nextState.i );
        },
        clickCb: function(){
            this.setState({
                i : this.state.i + 1
            })
        },
        render: function() {
            return <div onClick={this.clickCb}>state.i的值是:{this.state.i}</div>
        }
    });
    React.render(
            <Component1 />, div
    );
</script>
</body>
</html>
View Code

利用這個接口,我們可以在組件要重新渲染之前做一些需要的改動。

componentDidUpdate

Did表示完成時狀態,故該接口會在組件更新、重新渲染完畢了之后才觸發,它和 componentWillUpdate 一樣有着倆個參數來獲取新的 props 和 state。

我們繼續拿上一個例子來做修改:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>componentDidUpdate</title>
    <style>
        div:active{color:orangered;}
    </style>
    <script src="react.js"></script>
    <script src="JSXTransformer.js"></script>
</head>
<body>
<div id="a">123</div>
<script type="text/jsx">
    var div = document.getElementById('a');

    var Component1 = React.createClass({
        getInitialState: function(){
            return { i : 0 }
        },
        shouldComponentUpdate: function(nextProps, nextState){
            console.log( this.state.i, nextState.i );
            return nextState.i > 3 ? true : false; //返回true才會執行componentWillUpdate並重新渲染組件
        },
        componentDidUpdate: function(nextProps, nextState){
            console.log( '已經渲染完畢咯', this.state.i, nextState.i );
        },
        componentWillUpdate: function(nextProps, nextState){
            console.log( '還沒渲染哦', this.state.i, nextState.i );
        },
        clickCb: function(){
            this.setState({
                i : this.state.i + 1
            })
        },
        render: function() {
            return <div onClick={this.clickCb}>state.i的值是:{this.state.i}</div>
        }
    });
    React.render(
            <Component1 />, div
    );
</script>
</body>
</html>
View Code

執行如下:

三. Unmounting/組件移除相關:

componentWillUnmount 

在組件要被移除之前的時間點觸發,可以利用該方法來執行一些必要的清理,比如清除無效的定時器,或者清除在 componentDidMount 中創建的 DOM 元素等:
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>componentWillUnmount</title>
    <style>
        div:active{color:orangered;}
    </style>
    <script src="react.js"></script>
    <script src="JSXTransformer.js"></script>
</head>
<body>
<div id="a">123</div>
<div id="b"><p>這里是div2,點擊我會移除上面div的組件</p></div>
<script type="text/jsx">
    var div = document.getElementById('a'),
        div2 = document.getElementById('b');

    var Component1 = React.createClass({
        DOMArr : [],
        getInitialState: function(){
            return { i : 0 }
        },
        componentDidUpdate: function(nextProps, nextState){
            var dom = document.createElement('p');
            dom.innerText = this.state.i;
            div2.appendChild(dom);
            this.DOMArr.push(dom);
        },
        componentWillUnmount: function(){
            if(!this.DOMArr.length) return;
            var i = 0;
            while(i < this.DOMArr.length){console.log(i);
                div2.removeChild(this.DOMArr[i++]); //移除componentDidUpdate里添加過的DOM
            }
        },
        clickCb: function(){
            this.setState({
                i : this.state.i + 1
            })
        },
        render: function() {
            return <div onClick={this.clickCb}>state.i的值是:{this.state.i}</div>
        }
    });
    React.render(
            <Component1 />, div
    );

    div2.addEventListener('click',function(){
        React.unmountComponentAtNode(div) //點擊div2則卸載掉第一個div里的組件
    }, false)
</script>
</body>
</html>
View Code

執行如下:

四. getDefaultProps 和 getInitialState 

在《React 引領未來的用戶界面開發框架》一書中,還把 getDefaultProps 和 getInitialState 列入了組件生命周期的“實例化”階段。

getDefaultProps

該方法是所有我們提及的方法中最先觸發的,你可以在該方法里 return 一個對象來作為組件默認的Props值(當然如果父組件傳進來了props,則以傳進來的為主),它只在組件初次掛載到頁面上時觸發一次,即使你重新掛載了組件。

getInitialState 

這個在第一篇文章的時候就介紹過了,它用於給組件初始化state的值,調用該方法要求必須 return 一個對象或者null,否則會報錯。該方法在組件每次實例化(也就是掛載)的時候都會觸發。

我們來段簡單的代碼輔助理解:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>getDefaultProps 和 getInitialState</title>
    <style>
        div:active{color:orangered;}
    </style>
    <script src="react.js"></script>
    <script src="JSXTransformer.js"></script>
</head>
<body>
<div></div>
<div></div>
<script type="text/jsx">
    var diva = document.getElementsByTagName('div')[0],
            divb = document.getElementsByTagName('div')[1];
    var Component1 = React.createClass({
        getDefaultProps: function(){
            console.log('getDefaultProps');
            return { name : Date.now() }
        },
        getInitialState: function(){
            console.log('getInitialState');
            return null; //必須返回一個null或對象,否則會報錯
        },

        render: function() {
            console.log(Date.now());
            return <div name={this.props.name}>我只是一個安靜的div</div>
        }
    });
    React.render(
            {/* 觸發一次 getDefaultProps 和 getInitialState */}
            <Component1 />, diva
    );
    React.render(
            {/* getDefaultProps 和 getInitialState都不觸發 */}
            <Component1 />, diva
    );
    React.unmountComponentAtNode(diva);
    React.render(
            {/* 觸發一次getInitialState */}
            <Component1 name="a"/>, diva
    );
    React.render(
            {/* 觸發一次getInitialState */}
            <Component1/>, divb
    );
</script>
</body>
</html>
View Code

執行結果:

至此我們便學習完了React組件里的共九個聲明周期的接口,最后出道題給大家思考下,下面的代碼輸出的順序應該是什么呢:

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>題目</title>
    <style>
        div:active{color:orangered;}
    </style>
    <script src="react.js"></script>
    <script src="JSXTransformer.js"></script>
</head>
<body>
<script type="text/jsx">
    var Component1 = React.createClass({
        getDefaultProps: function(){
            console.log('getDefaultProps')
        },
        getInitialState: function(){
            console.log('getInitialState');
            return null
        },
        componentWillMount: function(){
            console.log('componentWillMount')
        },
        componentDidMount: function(){
            console.log('componentDidMount')
        },
        componentWillReceiveProps: function(){
            console.log('componentWillReceiveProps')
        },
        shouldComponentUpdate: function(){
            console.log('shouldComponentUpdate');
            return true;
        },
        componentWillUpdate: function(){
            console.log('componentWillUpdate')
        },
        componentDidUpdate: function(){
            console.log('componentDidUpdate')
        },
        componentWillUnmount: function(){
            console.log('componentWillUnmount')
        },
        render: function() {
            return <div>我只是一個安靜的div</div>
        }
    });
    React.render(
            <Component1 />, document.body
    );
    React.render(
            <Component1 />, document.body
    );
    React.unmountComponentAtNode(document.body)
</script>
</body>
</html>

建議思考完了再往下滾動看答案吧,如果想不起來,可以翻到文章前面在回顧一下,多溫習多思考,總是好習慣。

順便再提一下,本文的全部示例都可以在我的github上下載到。

關於上方問題的答案如下:

最后建議大家多實踐,不局限於看文章。也希望本文能對你有所幫助,共勉~

donate


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM