React 深刻系列4:组件的生命周期

React 深刻系列,深刻讲解了React中的重点概念、特性和模式等,旨在帮助你们加深对React的理解,以及在项目中更加灵活地使用React。

组件是构建React应用的基本单位,组件须要具有数据获取、业务逻辑处理、以及UI呈现的能力,而这些能力是要依赖于组件不一样的生命周期方法的。组件的生命周期分为3个阶段:挂载阶段、更新阶段、卸载阶段,每一个阶段都包含相应的生命周期方法。由于是深刻系列文章,本文不会仔细介绍每一个生命周期方法的使用,而是会重点讲解在使用组件生命周期时,常常遇到的疑问和错误使用方式。html

服务器数据请求

初学者在使用React时,经常不知道什么时候向服务器发送请求,获取组件所需数据。对于组件所需的初始数据,最合适的地方,是在componentDidMount方法中,进行数据请求,这个时候,组件完成挂载,其表明的DOM已经挂载到页面的DOM树上,即便获取到的数据须要直接操做DOM节点,这个时候也是绝对安全的。有些人还习惯在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,请求新闻详情数据
  }
}

若是进行数据请求的时机是由页面上的交互行为触发的,例如,点击查询按钮后,查询数据,这时只须要在查询按钮的事件监听函数中,执行数据请求便可,这种状况通常是不会有疑问的。服务器

更新阶段方法的调用

组件的更新是组件生命周期中最复杂的阶段,也是涉及到最多生命周期方法的阶段。app

正常状况下,当组件发生更新时,组件的生命周期方法的调用顺序以下:异步

componentWillReceiveProps -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate

// 组件收到新的props(props中的数据并不必定真正发生变化)-> 决定是否须要继续执行更新过程 -> 组件表明的虚拟DOM即将更新 -> 组件从新计算出新的虚拟DOM -> 虚拟DOM对应的真实DOM更新到真实DOM树中

父组件发生更新或组件自身调用setState,都会致使组件进行更新操做。父组件发生更新致使的组件更新,生命周期方法的调用状况同上所述。若是是组件自身调用setState,致使的组件更新,其生命周期方法的调用状况以下:函数

shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate

可见,这种状况下componentWillReceiveProps并不会被调用。fetch

当组件的shouldComponentUpdate返回false时,组件会中止更新过程,这时候生命周期方法的调用顺序以下:this

componentWillReceiveProps -> shouldComponentUpdate -> 结束

或(组件自身调用setState,致使的组件更新):spa

shouldComponentUpdate -> 结束

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 调用时,组件即将被卸载,setState是为了更新组件,在一个即将卸载的组件上更新state显然是无心义的。实际上,在componentWillUnmount中调用setState也是会抛出异常的。

render次数 != 浏览器界面更新次数

先看下面的一个例子:

class App extends React.Component {

  constructor(props) {
    super(props)
    this.state = {
      bgColor: "red"
    }
  }

  render() {
    var {bgColor} = this.state
    return (
      <div style = {{backgroundColor: bgColor}}> 
        Test
      </div>
    );
  }
  
  componentDidMount() {
    this.setState({
      bgColor: "yellow"
    })
  }
}

当咱们观察浏览器渲染出的页面时,页面中Test所在div的背景色,是先显示红色,再变成黄色呢?仍是直接就显示为黄色呢?

答案是:直接就显示为黄色!

这个过程当中,组件的生命周期方法被调用的顺序以下:

constructor -> componentWillMount -> render -> componentDidMount -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate

组件在挂载完成后,由于setState的调用,将当即执行一次更新过程。虽然render方法被调用了两次,但这并不会致使浏览器界面更新两次,实际上,两次DOM的修改会合并成一次浏览器界面的更新。React官网介绍componentDidMount方法时也有如下说明:

Calling setState() in this method will trigger an extra rendering, but it will happen before the browser updates the screen. This guarantees that even though the render() will be called twice in this case, the user won’t see the intermediate state.

这说明,组件render的次数 不必定等于 浏览器界面更新次数。虽然JS的执行和DOM的渲染分别由浏览器不一样的线程完成,但JS的执行会阻塞DOM的渲染,而上面的两次render是在一个JS事件周期内执行的,因此在两次render结束前,浏览器不会更新界面。

下篇预告:

React 深刻系列5:事件处理


个人新书《React进阶之路》已上市,请你们多多支持!
连接:京东 当当

图片描述

相关文章
相关标签/搜索