ReactJS入门(二)—— 组件的生命周期

若是你熟悉avalon,使用过 data-include-rendered 和 data-include-loaded 等回调方法,那么你会很好地理解React组件的各个生命周期。html

说白了其实就是React组件状态变化先后的时间点,咱们能够利用生命周期的接口在相应的时间点作回调操做。react

React的官方文档说起了以下几个组件的生命周期:git

Mounting/组件挂载相关: github

componentWillMount
componentDidMount

Updating/组件更新相关:app

componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate

Unmounting/组件移除相关:框架

componentWillUnmount 
 

下面咱们将经过一些实例来理解它们。顺便说下本文的示例均可以在个人github上下载到。dom

一. Mounting/组件挂载相关ide

componentWillMount学习

在组件挂载以前执行操做,但仅执行一次,即便屡次重复渲染该组件,或者改变了组件的state:this

<!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

相关文章
相关标签/搜索