6

React系列---React(一)初识React
React系列---React(二)组件的prop和state
React系列---之React(三)组件的生命周期


React严格定义了组件的生命周期,共3个过程:
1) 装载过程(Mount):组件第一次在DOM树中渲染的过程;
2) 更新过程(Update):组件被重新渲染的过程;
3) 卸载过程(Unmount):组件从DOM树中删除的过程。
clipboard.png

三种不同的过程,React库会依次调用组件的一些生命周期函数。所以,定义一个React组件,实际上就是定制这些生命周期函数。

组件装载过程

装载过程依次调用的生命周期函数:
constructor
getInitialState
getDefaultProps
componentWillMount
render
componentDidMount

constructor

ES6中每个类的构造函数,创造一个组件实例,当然会调用对应的构造函数。

并不是每个组件都需要定义构造函数。后面会看到无状态React组件是不需要定义构造函数的。

React组件需要构造函数,是为了以下目的:
1) 初始化state,因为生命周期中任何函数都有可能访问state,构造函数是初始化state的理想场所;
2) 绑定成员函数的this环境。

getInitialState和getDefaultProps

这2个函数,只有在通过React.createClass方法创造的组件类才会发生作用。这是过时的方法,ES6创造的组件中用不到。

假如用React.createClass定义组件Sample,设定内部状态foo初始值为bar,同时设定sampleProp的prop初始值为0,代码如下:

const Sample = React.createClass({
    getInitialState: function(){
        return {foo: 'bar'};
    },
    getDefaultProps: function() {
        return {sampleProp: 0}
    }
});

用ES6的话,在构造函数中给this.state赋值完成状态初始化,给类的属性defaultProps赋值指定props初始值:

class Sample extends React.Component {
    constructor(props) {
        super(props);
        this.state = {foo: 'bar'};
    }
}

Sample.defaultProps = {
    return {sampleProp: 0};
};

render

React组件可以忽略其他所有函数都不实现,但是一定要实现render函数,因为所有React组件的父类React.Component类对除render之外的生命周期函数都有默认实现。render函数并不做实际的渲染动作,它只负责返回一个JSX描述的结构,最终由React来操作渲染过程。

render函数应该是一个纯函数,完全根据this.state和this.props来决定返回的结果,而且不要产生任何副作用。在render函数中去调用this.setState是错误的,因为一个纯函数不应该引起状态的变化。

componentWillMount和componentDidMount

装载过程中,componentWillMount和componentDidMount分别在render之前和之后调用。

不过,通常不定义componentWillMount函数,因为顾名思义,它发生在将要装载的时候,这个时候一切都迟了,即使再调用this.setState()修改状态也不会引发重新绘制了。换句话说,所有可以在componentWillMount中做的事情,都可以提前到constructor中去做。可以认为这个函数存在的目的就是为了和componentDidMount对称。

而componentDidMount作用就大了。不过要注意的是,componentDidMount被调用时,前置render函数返回的东西必定已经完成了渲染,组件已经被“装载”到DOM树上了。

还是以ControlPanel为例,在ControlPanel中有三个Counter组件,我们修改Counter代码,让装载过程的所有生命周期函数都用console.log输出函数名和caption值,比如,componentWillMount函数如下:

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

在浏览器的console里我们能看到:

enter constructor: First
enter componentWillMount First
enter render First
enter constructor: Second
enter compomentWillMount: Second
enter render Second
enter constructor: Third
enter componentWillMount Third
enter render Third
enter componentDidMount First
enter componentDidMount Second
enter componentDidMount Third

可以清楚的看到,由于渲染需要一定的时间,所以三个组件的componentDidMount是在最后才连在一起被调用的。

componentWillMount和componentDidMount还有一个区别,就是componentWillMount可以在服务端被调用,也可以在浏览器端被调用;而componentDidMount只能在浏览器端被调用。

componentDidMount中,可通过AJAX获取数据来填充组件内容。在componentDidMount被调用时,组件已经被装载到DOM树了,也可以放心的让React和其他操纵DOM的库(如jQuery)配合工作了。

组件更新过程

当组件被装载到DOM树上之后,用户在网页上看到了第一印象,但是要提供更好的交互体验,就要让组件可以随着用户操作改变展现的内容,当props或state被修改时,就会引发组件的更新过程。

更新过程依次调用以下生命周期函数:
1) componentWillReceiveProps
2) shouldComponentUpdate
3) componentWillUpdate
4) render
5) componentDidUpdate

componentWillReceiveProps

当组件的props发生改变时会被调用。父组件的render被调用时,被渲染的子组件也会经历更新过程,不管父组件传给子组件的props有没有改变,都会触发子组件的componentWillReceiveProps。

我们在Counter组件类里增加这个函数定义,并在console输出一些文字:

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

在ControlPanel组件的render函数中,也做如下修改:

render() {
    console.log('enter ControlPanel render');
    return (
      <div style={style}>
        ...
        <button onClick={ () => this.forceUpdate() }>
          Click me to parent!
        </button>
      </div>
    );
}

除了在ControlPanel的render函数入口增加console输出,还增加了一个按钮,当这个按钮被点击时,调用this.forceUpdate(),每个React组件都可以通过forceUpdate()强行引发一次组件重绘。

在网页上点击父组件新增的重绘按钮,看到浏览器console输出:

enter ControlPanel render
enter componentWillReceiveProps First
enter render First
enter componentWillReceiveProps Second
enter render Second
enter componentWillReceiveProps Third
enter render Third

可以看到,父组件引发重绘后,首先是父组件ControllPanel的render被调用,随后依次3个Counter子组件的componentWillReceiveProps和render函数被调用。

然而,父组件传给三个子组件的props值一直没有变化,这就验证了componentWillReceiveProps并不只是当props值变化时才被调用,父组件render时,子组件的componentWillReceiveProps也会被调用。

在网页中,我们再尝试点击第一个Counter子组件的“+”按钮,可以看到浏览器console输出:

enter render First

明显,只有第一个子组件Counter的render函数被调用,因为组件的this.setState()函数不会引发componentWillReceiveProps调用。

shouldComponentUpdate(nextProps, nextState)

shouldComponentUpdate函数返回一个布尔值,告诉React库这个组件在这次更新过程中是否要继续。返回false会停止更新过程,就此结束,也不会引发后续的渲染了。

这个函数能够大大提升React组件的性能,不管React的组件渲染有多快,如果发现没有必要重新渲染,就干脆不要渲染。

修改Counter组件类增加shouldComponentUpdate函数的定义:

shouldComponentUpdate(nextProps, nextState) {
    return (nextProps.caption !== this.props.caption) ||
       (nextState.count !== this.state.count);
}

componentWillUpdate和componentDidUpdate

如果组件的shouldComponentUpdate返回true,接下来会依次调用componentWillUpdate、render和componentDidUpdate函数。

在介绍componentDidMount函数时,说到可以利用componentDidMount函数执行其他UI代码库,比如jQuery代码。那么现在,组件被更新时,也需要在componentDidUpdate函数再次调用jQuery代码。

组件卸载过程

React组件的卸载过程只涉及一个函数componentWillUnmount,这个函数适合做一些清理工作。这些清理工作往往和componentDidMount有关,比如你在componentDidMount中用非React的方法创造了一些DOM元素,如果撒手不管会造成内存泄漏,那就需要在componentWillUnmount中把这些DOM元素清理掉。


代码:https://github.com/zhutx/reac...


React系列---React(一)初识React
React系列---React(二)组件的prop和state
React系列---之React(三)组件的生命周期


zhutianxiang
1.5k 声望328 粉丝