每个组件在网页中有自己的生命周期,生命周期可能会经历如下三个过程:
- 装载过程(Mount):第一次在DOM树中渲染的过程
- 更新过程(Update):组件被重新渲染的过程
- 卸载过程(Unmount):组件从DOM中删除的过程
三种不同的过程,React库会一次调用组件的一些成员函数,这些函数被称为组件的生命周期。所以,要定制一个React组件,实际上就是定制这些生命周期函数。
装载过程
当组件第一次被渲染时,依次调用如下函数:
constructor
在ES6的Class课程中,我们就已经详细的介绍过constructor相当于类的构造函数。比如:
class Button extends React.Component {
constructor() {
...
}
....
}
以上代码的constructor就相当于
function Button() {....}
并不是每个组件都需要创建constructor,无状态的组件就不需要定义构造函数。一个React组件需要创建构造函数,往往因为如下原因:
- 初始化state:组件生命周期中任何函数都可能需要访问state,constructor作为生命周期中第一个被调用的函数,自然也就成了初始化state的理想之地。
- 绑定成员函数的this环境
ES6的课程中我们介绍过Class的super:
class ColorPoint extends Point {
constructor(x, y, color) {
// 相当于Point.call(this, x, y);
// 调用父类的constructor(x, y)
super(x, y);
this.color = color;
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
....
}
}
在子类的构造函数中,只有调用super之后才可以使用this。因为子类实例的构建,是基于对父类实例加工(即子类继承父类 Point.call(this, x, y) ),只有super方法才能返回父类。在上面的例子中,我们通过bind方法,让当前实例中的handleClick函数被调用时,this始终指向当前的组件实例。
render
一个React组件可以不定义其他所有函数,但一定要实现render函数,因为React.Component类对除render之外的生命周期函数都有默认实现。
render函数不做实际的渲染工作,它只是返回一个JSX描述的结构。最终由React来操作渲染过程。如果一个组件不需要做任何渲染,那就让render返回null或false。
render是一个纯函数,完全根据state和props来决定返回的结果,且不可在render中改变state,也就是说不可在render中调用this.setState。
componentWillMount和componentDidMount
componentWillMount和componentDidMount分别在render函数前后调用,正好做render函数前后必要的工作。
componentWillMount发生在“将要装载”的时候,这个时候没有任何渲染结果,我们通常在服务端调用componentWillMount;在浏览器端,能在componentWillMount做的事情,我们都可以放在constructor中去完成。
请看如下代码:
class ControlPanel extends Component {
render() {
console.log('ControlPanel render');
return (
<div>
<Counter caption="First"/>
<Counter caption="Second" />
<Counter caption="Third" />
</div>
);
}
}
class Counter extends Component {
constructor(props) {
console.log('constructor: ' + props.caption);
......
}
componentWillMount() {
console.log('componentWillMount:' + this.props.caption)
}
render() {
console.log('render ' + this.props.caption);
.....
);
}
}
结果如下:
由于我们不做服务端渲染,所以在实际开发中,更关注componentDidMount。componentDidMount发生在组件已经被“装载”到DOM树的时候。此时,我们可以获取DOM。如果我们想让React和其他UI库配合,比如jQuery。需要注意的是:render函数被调用之后,componentDidMount并不会被立刻调用。
class ControlPanel extends Component {
render() {
console.log('ControlPanel render');
return (
<div>
<Counter caption="First"/>
<Counter caption="Second" />
<Counter caption="Third" />
</div>
);
}
}
class Counter extends Component {
constructor(props) {
console.log('constructor: ' + props.caption);
......
}
componentWillMount() {
console.log('componentWillMount:' + this.props.caption)
}
componentDidMount() {
console.log('componentDidMount:' + this.props.caption)
}
render() {
console.log('render ' + this.props.caption);
.....
);
}
}
大家先想一下结果,然后在看结果图:
我靠!怎么和我们想象的不一样,三个componentDidMount怎么都跑到最后才出现?
原来,render函数本身并不往DOM树上直接渲染,它所做的仅仅是返回JSX。React库要把所有组件返回的结果综合起来,才能知道如何产生对应的DOM修改。
更新过程
当组件被装载到DOM树之后,要提供更好的交互体验,就要让该组件可以随着用户操作改变展现内容。当props和state被修改时,就会引发组件的更新过程。
在16.3版本之前, 更新过程React的生命周期见下图:
据官网介绍,17版本之后,在更新过程中React的生命周期会发生很大的变化,有些函数会被更改甚至废弃,这里我们只介绍最重要且最常用的函数shouldComponentUpdate。
shouldComponentUpdate(nextProps, nextState)
除了render函数之外,shouldComponentUpdate应该是React组件生命周期中最重要的函数。render函数决定了组件该渲染什么,shouldComponentUpdate决定了组件什么时候不需要渲染,这一点对于提高组件性能相当重要!
render和shouldComponentUpdate是React组件生命周期中有且仅有需要返回结果的两个函数。render返回结果用于渲染组件,shouldComponentUpdate返回一个布尔值,告诉组件需不需要继续这次更新。shouldComponentUpdate返回true,则继续更新,调用render函数;返回false,则停止更新,不调用render函数。
shouldComponentUpdate接受两个参数:nextProps和nextState,外加this.props和this.state来判断是返回true还是false。
在前面的例子中,我们点击re-render按钮,输出结果:
现在给counter组件添加shouldComponentUpdate函数:
shouldComponentUpdate(nextProps, nextState) {
return nextProps.caption !== this.props.caption || nextState.count !== this.state.count
}
再点击re-render按钮,结果如下图:
可见,当counter组件上的caption或count属性不发生变化的时候,counter组件就不会再重复渲染。
卸载过程
React组件卸载过程只涉及一个函数componentWillUnmount。该函数在组件从DOM树上删掉之前调用,所以适合做一些清理工作。componentWillUnmount中的工作往往跟componentDidMount有关。如前面所说的在componentDidMount中引入jQuery创建DOM元素,那么在componentWillUnmount中就需要将这些创建的DOM元素清除掉,否则就会造成内存泄露。