react生命周期

Posted by XuBaoshi on August 29, 2017

react生命周期

所谓的生命周期,就是一个对象从开始生成到最后消亡所经历的状态。
react组件的生命周期可以分为三个部分:实例期、存在期、和销毁期。这里对react的服务端渲染不做阐述。只针对客户端渲染。
由于未来React版本去除了React.createClass方法,下面的例子使用es5的语法(将来主要使用es6 类Class方式):

实例期

当组件在客户端被第一次创建时,依次调用getDefaultProps、getInitialState、componentWillMount、render、componentDidMonut。

getDefaultProps()

getDefaultPops是对于组件类来说只调用一次,该类的所有后续应用将不会被调用,该方法返回默认的props。如果父组件没有传递其他生命周期函数所需要的属性,将默认采用getDefaultProps返回的属性,通过this.props使用。

var ReactDOM = require('react-dom');
var createReactClass = require('create-react-class');

var A = createReactClass({
    getDefaultProps: function () {
        console.log('A:default props');
        return {
            name: 'hh',
            age: 20
        }
    },
    render: function () {
        console.log('A render fn');
        return (
            <h1>name:{this.props.name} age:{this.props.age}</h1>
        )
    }
});

// 1. basic
ReactDOM.render(<A></A>, document.getElementById('root'));

// 2.getDefaultProps只调用一次
var B = createReactClass({
     render: function () {
         return (
             <div>
                 <A></A>
                 <A></A>
             </div>       
         )
     }
 });
 ReactDOM.render(<B></B>, document.getElementById('root'));

// 3.设置props的其他方式
// 除了getDefaultProps方法以下方法也可以设置props
// 3.1 组件挂载时设置组件属性,优先级高于getDefaultProps方法返回的对象属性
ReactDOM.render(<A name='tt' age='22'></A>, document.getElementById('root'));

getInitialState()

对于组件的每一个实例来说,用来初始化每个实例的state,在这个方法中可以访问组件的props。state与props的区别在于,state存在于组件实例的内部而props存在于组件实例之间。
getInitialState和getDefaultPops 的调用是有区别的,getDefaultPops 是对于组件类来说只调用一次,后续该类的应用都不会被调用,而getInitialState 是对于每个组件实例来讲都会调用,并且只调一次。

var React = require('react');
var ReactDOM = require('react-dom');
var createReactClass = require('create-react-class');

var A = createReactClass({
    getInitialState: function () {
        return {
            active: false
        }
    },
    render: function () {
        return (
            <h1>{this.state.active ? 'Yes' : 'No'}</h1>
        )
    }
});

// 1.basic
ReactDOM.render(<A></A>,document.getElementById('root'));

// 2. getInitialState 可以访问组件的props
var B = createReactClass({
    getDefaultProps:function(){
        console.log('this is B default props');
        return {
            name:'hh'
        }
    },
    getInitialState: function () {
        console.log('this is B initial state');
        return {
            propsName:this.props.name,
            active: false
        }
    },
    render: function () {
        return (
            <h1>{this.state.active ? 'Yes' : 'No'} ,this is {this.state.propsName}</h1>
        )
    }
});

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

// 3. getInitialState 每个组件实例来讲都会调用,并且只调一次
var C = createReactClass({
    render: function(){
        return (
            <div>
                <B></B>
                <B></B>
            </div>
        )
    }
});
ReactDOM.render(<C></C>,document.getElementById('root'));

// 4. state存在于组件实例的内部而props存在于组件实例之间
var D = createReactClass({
    getDefaultProps:function(){
        return {
            name:`props${Math.random()}`
        }
    },
    getInitialState: function () {
        return {
            age:`state${Math.random()}`
        }
    },
    render: function () {
        console.log(`props: ${this.props.name},state: ${this.state.age}`);
        return (
            <h1>{this.props.name} ,{this.state.age}</h1>
        )
    }
});
var E = createReactClass({
    render:function(){
        return (
            <div>
                <D></D>
                <D></D>
            </div>
        )
    }
});
ReactDOM.render(<E></E>,document.getElementById('root'));

// 5.不要使用this.state直接修改state,使用this.setState
// 使用this.setState后依次调用以下方法shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate
var F = createReactClass({
    getInitialState: function () {
        return {
            age:20
        }
    },
    shouldComponentUpdate:function(){
        console.log('shouldComponentUpdate');
        return true;
    },
    componentWillUpdate:function(){
        console.log('componentWillUpdate');
    },
    componentDidUpdate:function(){
        console.log('componentDidUpdate');
    },
    changeHandle:function(){
        this.setState({age:new Date().getSeconds()});
    },
    render: function(){
        console.log('render');
        return (
            <div>
                <h1>{this.state.age}</h1>
                <button onClick={this.changeHandle}>change</button>
            </div>
        )
    }
});
ReactDOM.render(<F></F>,document.getElementById('root'));

componentWillMount()

该方法在首次渲染之前调用,也是再 render 方法调用之前修改 state 的最后一次机会。

var React = require('react');
var ReactDOM = require('react-dom');
var createReactClass = require('create-react-class');
var A = createReactClass({
    getInitialState: function () {
        return {
            name: 'hh'
        }
    },
    componentWillMount: function () {
        this.setState({
            name: 'll'
        });
    },
    render: function () {
        return (
            <h1>{this.state.name}</h1>
        )
    }
});

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

render()

render方法创建一个虚拟的DOM(Visual DOM),它并不是一个真正的DOM,执行ReactDOM.render方法才会生成真实的DOM。
render方法是唯一一个必须的方法。

var React = require('react');
var ReactDOM = require('react-dom');
var createReactClass = require('create-react-class');

var Container = createReactClass({
    render: function () {
        return (
            <div>
                Hello World!
            </div>
        );
    }
});
ReactDOM.render(<Container></Container>, document.getElementById('root'));

使用render方法需要注意以下几点:

  • 只能通过 this.props 和 this.state 访问数据。
  • render方法可以返回null、false、或任何React组件
  • 只能return一个顶级组件,不可以多个。
  • 不可以修改组件的状态或者DOM的输出。

componentDidMonut()

该方法被调用时,已经渲染出真实的DOM,此时我们可以访问DOM对象,或请求ajax(此时最为合适)。

var React = require('react');
var ReactDOM = require('react-dom');
var createReactClass = require('create-react-class');
var $ = require('jquery');

// 1. dom树生成
var A = createReactClass({
    render:function(){
        return (
            <div className="js-wrap">
                Hello World!
            </div>
        )
    },
    componentDidMount:function(){
        var $wrap = document.getElementsByClassName('js-wrap')[0];
        console.log($wrap.textContent);
    }
});

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

// 2.ajax
var B = createReactClass({
    getInitialState:function(){
        return {
            list:[]
        }
    },
    render:function(){
        var str = this.state.list.map(function(topic){
            return (
                <p key={topic.id + new Date().getTime()}>{obj.id}</p>
            )
        })
        return (
            <div>
                {str}
            </div>
        )
    },
    componentDidMount:function(){
        let _this = this; 
        let url = 'https://cnodejs.org/api/v1/topics?limit=30';
        $.get(url,function(data){
            _this.setState({list:data.data});
        })
    }
});
ReactDOM.render(<B></B>,document.getElementById('root'));

// 3.refs
// refs用处
// 1.可以用于触发焦点事件、文本选择及媒体播放等
// 2.触发动画
// 3.集成第三方库(涉及操作DOM的)
// 4.建议不要过度使用

// 3.1 basic
var C = createReactClass({
    render:function(){
        return (
            <input ref="textInput" />
        )
    },
    componentDidMount:function(){
        this.refs.textInput.focus();
    }
});
// ReactDOM.render(<C></C>,document.getElementById('root'));

// 3.2 别名
var D = createReactClass({
    render:function(){
        return (
            <input ref={(input)=>{this.textInput = input}} />
        )
    },
    componentDidMount:function(){
        this.textInput.focus();
    }
});
// ReactDOM.render(<D></D>,document.getElementById('root'));

// 3.3 refs 无状态组件
// 如果组件之间ref,ref则表示对应组件的实例  
// 无状态组件 无法使用refs,因为它没有实例
// 含有生命周期的组件
var E = createReactClass({
    render:function(){
        return (
            <input ref={input=>{this.textInput=input}} />
        )
    }
});

var F = function(){
    return (
        <input  ref={input=>{this.textInput=input}} />
    )
};

var G = createReactClass({
    render:function(){
        return (
            <div>
                <E ref={(input) => {this.E = input}} value="E"></E>
                <F ref={(input) => {this.F = input}} value="F"></F>
            </div>
        )
    },
    componentDidMount:function(){
        // 下述代码是推荐的 这样导致了G组件与子组件的强耦合。
        this.E.textInput.focus();
        console.log(this.E);
        console.log(this.F);
    }
});
// ReactDOM.render(<G></G>,document.getElementById('root'));

// 3.4 通过props暴露DOM给父组件
var H = createReactClass({
    render:function(){
        return (
            <input  ref={this.props.inputRef} />
        )
    }
});
var I = function(props){
    return (
        <input  ref={props.inputRef} />
    )
};
var J = createReactClass({
    render:function(){
        return (
            <div>
                <H inputRef={(input) => {this.textInputH = input}}></H>
                <I inputRef={(input) => {this.textInputI = input}}></I>
            </div>
        )
    },
    componentDidMount:function(){
        this.textInputH.focus();
        this.textInputI.focus();
    }
});
ReactDOM.render(<J></J>,document.getElementById('root'));

存在期

componentWillRecevieProps(nextProps)

组件的 props 属性可以通过父组件来更改,如果依赖于父组件的props被修改,componentWillReceiveProps 将被调用,接下来依次调用shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate。

var React = require('react');
var ReactDOM = require('react-dom');
var createReactClass = require('create-react-class');

var A = createReactClass({
    getInitialState:function(){
        return {
            age:20
        }
    },
    render: function () {
        return (
            <div>
                <h2>{this.props.name}</h2>
                <h3>{this.state.age}</h3>
            </div>
        )
    },
    componentWillReceiveProps: function (nextProps) {
        console.log(this.state);
        console.log(this.props);
        console.log('nextProps:');
        console.log(nextProps);
        this.setState({age:30});
    }
});

var B = createReactClass({
    getInitialState:function(){
        return {
            name:'hh'
        }
    },
    render:function(){
        return (
            <div>
                <A name={this.state.name}></A>
                <button onClick={this.clickHandle}>click</button>
            </div>
        )
    },
    clickHandle:function(){
        this.setState({name:'ll'});
    }
});
ReactDOM.render(<B></B>,document.getElementById('root'));

shouldComponentUpdate(nextProps,nextState)

当实例化后组件的props、state发生变化时将调用以下方法:shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate。
当组件的props、state发生改变时该方法执行,该方法传入两个参数nextProps(变更后的props)及nextState(变更后的state),按照规则(视业务规则而定)返回true or false,决定组件是否需要重新渲染。。如果返回false,则 componentWillUpdate、render、componentDidUpdate将不会执行。 后期做性能优化时,shouldComponentUpdate是着手点。

var React = require('react');
var ReactDOM = require('react-dom');
var createReactClass = require('create-react-class');

// 1.return false 
// name修改后不重新渲染
var A = createReactClass({
    getInitialState: function () {
        return {
            name:'hh'
        }
    },
    render: function () {
        return (    
            <div>
                <h2>{this.state.name}</h2>
                <button onClick={this.clickHandle}>change</button>
            </div>
        )
    },
    shouldComponentUpdate: function (nextProps, nextState) {
        console.log('shouldComponentUpdate!');
        return false;
    },
    componentWillUpdate:function(nextProps, nextState){
        console.log('this.props:');
        console.log(this.props);
        console.log('this.state:');
        console.log(this.state);
        console.log('nextProps:');
        console.log(nextProps);
        console.log('nextState:');
        console.log(nextState);
    },
    clickHandle: function () {
        this.setState({name:'ll'});
    }
});
ReactDOM.render(<A></A>,document.getElementById('root'));

// 2.props
// 只有当age发生变化 shouldComponentUpdate才会返回true
// 修改父组件的age componentWillReceiveProps
var B = createReactClass({
    getInitialState: function () {
        return {
            name:'hh'
        }
    },
    render: function () {
        return (    
            <div>
                <h2>{this.state.name}</h2>
                <h3>{this.props.age}</h3>
                <button onClick={this.clickHandle}>changeName</button>
            </div>
        )
    },
    componentWillReceiveProps:function(nextProps, nextState){
        console.log('componentWillReceiveProps!');
        this.setState({name:'tt'});
    },
    shouldComponentUpdate: function (nextProps, nextState) {
        console.log('this.props:');
        console.log(this.props);
        console.log('this.state:');
        console.log(this.state);
        console.log('nextProps:');
        console.log(nextProps);
        console.log('nextState:');
        console.log(nextState);
        if(nextProps.age != this.props.age){
            console.log('shouldComponentUpdate return  true');
            return true;
        } else {
            console.log('shouldComponentUpdate return  false');
            return false;
        }
    },
    clickHandle: function () {
        this.setState({name:'ll'});
    }
});

var C = createReactClass({
    getInitialState: function () {
        return {
            age: 20
        }
    },
    render:function(){
        return (
            <div>
                <B age={this.state.age}></B>
                <button onClick={this.clickHandle}>changeAge</button>
            </div>
        )
    },
    clickHandle: function () {
        this.setState({age:30});
    }
});

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

componentWillUpdate(nextProps,nextState)

在组件接收到了新的 props 或者 state 即将进行重新渲染前,componentWillUpdate(object nextProps, object nextState) 会被调用。有一点必须注意不要在该函数内调用this.setState方法。

var React = require('react');
var ReactDOM = require('react-dom');
var createReactClass = require('create-react-class');

var A = createReactClass({
    getInitialState: function () {
        return {
            name:'hh'
        }
    },
    render: function () {
        return (    
            <div>
                <h2>{this.state.name}</h2>
                <button onClick={this.clickHandle}>change</button>
            </div>
        )
    },
    shouldComponentUpdate: function (nextProps, nextState) {
        var num = Math.random() ;
        if(Math.random() > 0.5){
            console.log(true + '  shouldComponentUpdate: ' + num);
            return true;
        }else{
            console.log(false + '  shouldComponentUpdate: ' + num);
            return false;
        }
        // 如果为true 会一直循环走生命周期函数
        // return true;
    },
    componentWillUpdate:function(nextProps, nextState){
        this.setState({name:'ttt'});
        console.log('this.state:');
        console.log(this.state);
        console.log('nextState:');
        console.log(nextState);
    },
    clickHandle: function () {
        this.setState({name:'ll'});
    }
});
ReactDOM.render(<A></A>,document.getElementById('root'));

componentDidUpdate()

这个方法和 componentDidMount 类似,在组件重新被渲染之后,componentDidUpdate(object prevProps, object prevState) 会被调用。可以在这里访问并修改 DOM。

销毁期

componentWillUnmount()

组件将要移除时调用的函数, 在componentDidMount 中添加的任务都需要在该方法中撤销,如创建的定时器或事件监听器等。撤销后如果再次使用该组件那么该组件的生命周期从getInitialState => componentWillMount => render =>componentDidMount重新实例化。

// 下面代码由A组件切换至B组件后,A执行componentWillUnmount方法取消定时器timer
var React = require('react');
var ReactDOM = require('react-dom');
var createReactClass = require('create-react-class');

var timer;
var A = createReactClass({
    render: function () {
        return (
            <h1>A</h1>
        )
    },
    componentDidMount: function () {
        timer = setInterval(function () {
            console.log('this is A');
        }, 2000);
    },
    componentWillUnmount: function () {
        console.log('componentWillunmount for A');
        clearInterval(timer);
    }
});

var B = createReactClass({
    render: function () {
        return (
            <h1>B</h1>
        )
    }
});

var C = createReactClass({
    getInitialState: function () {
        return {
            type: 'A'
        }
    },
    render: function () {
        if (this.state.type == 'A') {
            return (
                <div>
                    <A></A>
                    <button onClick={this.clickHandle}>change1</button>
                </div>

            )
        } else {
            return (
                <div>
                    <B></B>
                    <button onClick={this.clickHandle}>change2</button>
                </div>

            )
        }
    },
    clickHandle: function () {
        if (this.state.type == 'A') {
            this.setState({ type: 'B' });
        } else {
            this.setState({ type: 'A' });
        }

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

类组件

上述代码主要讲述的是使用es5语法实现react组件,但在react官方文档中推荐ES6类绑定,前面之所以使用es5语法主要是为了方便讲述每一个生命周期函数,下面是有关class组件的相关内容及需要注意的地方:

在es6中一个React组件是用一个class来表示的,语法如下:

// 继承React.Component来实现
class A extends React.Component {
    //...
}

import React from 'react'
import ReactDOM from 'react-dom'

class A extends React.Component {
    render() {
        return (
            <h1>A</h1>
        )
    }
}
ReactDOM.render(<A></A>,document.getElementById('root'));

constructor(props,context)

ps:es6类组件内默认constructor方法,参数为props,如果声明组件时不添加constructor默认执行:

constructor(props){
    super(props)
}

如果声明组件是添加了constructor,子类必须在constructor方法中调用super方法,否则新建实例时会报错。

类组件的state及props

使用es6类的方式声明React组件中state及props的声明较es5的语法有些区别如下:

state

es5使用getInitialState方法返回初始state,但在es6类组件中需通过在constructor方法内使用this.state初始化state。

// class组件
class B extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            name: 'hh'
        }
    }
    render() {
        return (
            <h1>
                {this.state.name}
            </h1>
        )
    }
}

// es5 组件
var C = createReactClass({
    getInitialState: function () {
        return {
            name: 'll'      
        }
    },
    render:function(){
        return (
            <h2>{this.state.name}</h2>
        )
    }
})
class D extends React.Component{
    render(){
        return(
            <div>
                <B></B>
                <C></C>
            </div>
        )
    }
}
ReactDOM.render(<D></D>, document.getElementById('root'));

props

es5使用getDefaultProps方法返回初始props,es6的类组件初始化默认props略有不同。

// 1 在组件内部的使用static
static defaultProps = {
    name: ...
}

// 2 在组件外部
Hello.defaultProps = {
    name: ...
}

在组件内的写法需要注意的是static是es7的写法需要添加babel-preset-stage-x。

// 外部
class L extends React.Component{
    render(){
        return (
            <h1>{this.props.name}</h1>
        )
        
    }
}
L.defaultProps = {
    name:'hh'
}

class M extends React.Component{
    static defaultProps = {
        name:'ll'
    }
    render(){
        return (
            <h1>{this.props.name}</h1>
        )
        
    }
}

class O extends React.Component{
    render(){
        return(
            <div>
                <L></L>
                <M></M>
            </div>
        )
    }
}

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

类组件的this

使用es5的React.createClass(v16会被废弃)或 ‘create-react-class’,除了生命周期的钩子函数可以使用this(实例对象),自定义的方法同样可以使用this,是因为react本身帮我们绑定了this,才让我们不用手动去绑定this就能正确的使用。但es6的类组件除了生命周期函数,自定义的函数是不会进行this绑定的。需要手动绑定。

// 可以访问到this
var E = createReactClass({
    render:function(){
        return (
            <button onClick={this.clickHandle}>es5</button>
        )
    },
    clickHandle:function(){
        console.log(this);
    }
});

// 不可以访问到this为null
class F extends React.Component{
    render(){
        return (
            <button onClick={this.clickHandle}>es6</button>
        )
        
    }
    clickHandle(){
        console.log(this);
    }
}

class G extends React.Component{
    render(){
        return(
            <div>
                <E></E>
                <F></F>
            </div>
        )
    }
}

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

es6的类组件绑定this可以在constructor函数内部绑定this,也可以使用箭头函数等。参考链接 [https://segmentfault.com/a/1190000006133727][https://segmentfault.com/a/1190000006133727]。如下:该文章推荐在constructor函数内部绑定this。

// 4.2.1 render() .bind(this)
// bad:每次render都要生成一个匿名函数
class H extends React.Component{
    render(){
        return (
            <button onClick={this.clickHandle.bind(this)}>render-bind</button>
        )
        
    }
    clickHandle(){
        console.log(this);
    }
}

// 4.2.2 arrow function
// bad: 每次render都要生成一个箭头函数
class I extends React.Component{
    render(){
        return (
            <button onClick={() => this.clickHandle()}>arrow-bind</button>
        )
        
    }
    clickHandle(){
        console.log(this);
    }
}

// 推荐使用这个
class J extends React.Component{
    constructor(props){
        super(props)
        this.clickHandle = this.clickHandle.bind(this)
    }
    render(){
        return (
            <button onClick={this.clickHandle}>constructor-bind</button>
        )
        
    }
    clickHandle(){
        console.log(this);
    }
}

class K extends React.Component{
    render(){
        return(
            <div>
                <H></H>
                <I></I>
                <J></J>
            </div>
        )
    }
}

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