一、什么是生命周期?
生命周期就是指一个对象的生老病死。
而组件的生命周期则是这个组件从创建到销毁的一个过程。在这个过程中会有不同的阶段,从而会产生一些对应的生命周期函数来供我们使用,以便能够进行一些渲染、更新等处理。
react的生命周期可以分为三个阶段:
- 初始化(挂载)
- 存在期(更新)
- 销毁 (卸载)
在源码里对应的就是MOUNTING、RECEIVE_PROPS、UNMOUNTING
二、生命周期阶段详解
1、初始化阶段(挂载)
(1)首先通过constructor方法完成对props和state的初始化。其中props是react从父组件继承过来的,state需要我们自己去定义。
- getDefaultProps:获取实例的默认props(即使没有生成实例,组件的第一个实例被初始化CreateClass的时候调用,只调用一次,)
- getInitialState:获取每个实例的初始化state(每个实例自己维护)
(2)之后是componentWillMount(),这个方法执行的时间点是在props和state初始化之后,render之前。这个时候可以在里面执行this.setState()操作,执行setState()并不会导致页面的渲染,只是单纯的state的合并操作。这个方法只执行一次(看图:初始化阶段的方法都是只执行一次的)。
- componentWillMount:组件即将被装载、渲染到页面上
(3)render:返回JSX,这个JSX会调用React.createElement(),生产虚拟DOM节点,之后再经过一系列复杂的过程,成为真实DOM(只能通过this.props和this.state访问数据;不允许修改状态和DOM输出;可以返回null或者false,就是什么都不渲染
)
(4)componentDidMount:这时已经挂载到了DOM上
,所以在这个方法里面可以操作DOM。此时已可以使用其他类库来操作这个DOM。在服务端中,该方法不会被调用。
当我们需要请求外部接口数据,数据请求一般都在这
里处理。但是在这里写一堆ajax你不觉得很混乱吗,交给redux吧。
有些人还习惯在constructor或者componentWillMount中,进行数据请求,认为这样可以更快的获取到数据,但它们相比componentDidMount的执行时间,提前的时间实在是太微乎其微了。另外,当进行服务器渲染时(SSR),componentWillMount是会被调用两次的,一次在服务器端,一次在客户端,这时候就会导致额外的请求发生。
组件进行数据请求的另一种场景:由父组件的更新导致组件的props发生变化,如果组件的数据请求依赖props,组件就需要重新进行数据请求。例如,新闻详情组件NewsDetail,在获取新闻详情数据时,需要传递新闻的id作为参数给服务器端,当NewsDetail已经处于挂载状态时,如果点击其他新闻,NewsDetail的componentDidMount并不会重新调用,因而componentDidMount中进行新闻详情数据请求的方法也不会再次执行。这时候,应该在componentWillReceiveProps中,进行数据请求:
componentWillReceiveProps(nextProps){
if(this.props.newId !== nextProps.newsId){
fetchNewsDetailById(nextProps.newsId)
// 根据最新的新闻id,请求新闻详情数据
}
}
如果进行数据请求的时机是由页面上的交互行为触发的,例如,点击查询按钮后,查询数据,这时只需要在查询按钮的事件监听函数中,执行数据请求即可,这种情况一般是不会有疑问的。
2、存在期阶段(更新)
上述的初始化阶段已过去,剩下的都是props和state的改变导致的不断Update的过程,所以说初始化的生命周期函数都只执行一次。
这个时候我们的组件已经渲染出来了,用户也能够看到了,随着用户的操作,比如点击按钮,发送信息等就会导致有新的props或者state从上层甚至上上层流入到我们刚刚新鲜出炉的组件里,这时就会触发组件的更新,导致UI视图的重绘。
- componentWillReceiveProps:组件将要接收到属性的时候调用(赶在父组件修改真正发生之前,可以修改属性和状态)
从state或props改变后最终都会交汇到一点shouldComponentUpdate,它是生命周期里重绘组件的阀门,它的返回值是一个boolean,默认返回true,如果返回false,那就简单多了,因为接下来的步骤都不会执行了。
shouldComponentUpdate:决定是否需要继续执行更新过程
若返回false:则componentWillUnmount -> 结束
如果返回true:咱们继续。componentWillUpdate:不能修改属性和状态
它和componentWillMount差不多,就是重绘前的准备工作。唯一需要注意的是,这里面不能进行setState()或者更新props。因为这个函数自己就会把最新的state和props设置到this.state和this.props中。
把state和props的更新放到componmentWillReceiveProps里吧。componentDidUpdate:它和componmentDidMount差不多,也是可以获取真实DOM,可以用第三方库操作DOM
3、销毁阶段(卸载)
- componentWillUnmount:开发者需要来销毁(组件真正删除之前调用,比如计时器和事件监听器)
三、setState的时机
组件的生命周期方法众多,哪些方法中可以调用setState更新组件状态?哪些方法中不可以呢?
可以的方法
componentWillMount、componentDidMount、componentWillReceiveProps、componentDidUpdate
注意:
1、componentWillMount
同步
调用setState不会
导致组件进行额外的渲染
,组件经历的生命周期方法依次是componentWillMount -> render -> componentDidMount,组件并不会因为componentWillMount中的setState调用再次进行更新操作。异步
调用setState,组件是会
进行额外的更新操作
。不过实际场景中很少
在componentWillMount中调用
setState,一般
可以通过直接在constructor中定义
state的方式代替。
2、一般情况下,当调用setState后,组件会执行一次更新过程,componentWillReceiveProps等更新阶段的方法会再次被调用,但如果在componentWillReceiveProps中调用setState,并不会额外导致一次新的更新过程,也就是说,当前的更新过程结束后,componentWillReceiveProps等更新阶段的方法不会再被调用一次。(注意,这里仍然指同步
调用setState,如果是异步调用,则会导致组件再次进行渲染
)
3、componentDidUpdate中调用setState要格外小心,在setState前必须有条件判断,只有满足了相应条件,才setState,
否组组件会不断执行更新过程,进入死循环
(因为setState会导致新一次的组件更新,组件更新完成后,componentDidUpdate被调用,又继续setState,死循环就产生了。)
不可以的方法
其他生命周期方法都不能调用setState,主要原因有两个:
1、产生死循环。
例如,shouldComponentUpdate、componentWillUpdate 和 render 中调用setState,组件本次的更新还没有执行完成,又会进入新一轮的更新,导致不断循环更新,进入死循环。
2、无意义。componentWillUnmount 调用时,组件即将被卸载。实际上,在componentWillUnmount中调用setState也是会抛出异常的。
四、render次数 != 浏览器界面更新次数
先看下面的一个例子:
class App extends React.Component {
constructor(props){
super(props);
this.state = {
bgColor:'red'
}
}
componentDidMount(){
this.setState({
bgColor:'yellow'
});
}
render(){
var {bgColor} = this.state;
return(
<div style={{backgroundColor:bgColor}}>
Test
</div>
);
}
}
当我们观察浏览器渲染出的页面时,页面中Test所在div的背景色,是先显示红色,再变成黄色呢?还是直接就显示为黄色呢?
答案是:直接就显示为黄色!
这个过程中,组件的生命周期方法被调用的顺序如下:
constructor -> componentWillMount -> render -> componentDidMount -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
组件在挂载完成后,因为setState的调用,将立即执行一次更新过程。虽然render方法被调用了两次,但这并不会导致浏览器界面更新两次,实际上,两次DOM的修改会合并成一次浏览器界面的更新。
组件render的次数 不一定等于 浏览器界面更新次数。虽然JS的执行和DOM的渲染分别由浏览器不同的线程完成,但JS的执行会阻塞DOM的渲染,而上面的两次render是在一个JS事件周期内执行的,所以在两次render结束前,浏览器不会更新界面。