2-4-组件生命周期

这一节课我们来了解一下 React 组件的生命周期,内容会涉及到一些原理的解释,看不懂也没关系,实际运用的时候只需要记住几个主要的生命周期函数方法(土话其实就叫钩子方法)即可。

  • React是如何渲染组件的
  • 渲染组件时会触发的生命周期方法
    • 挂载流程
    • 更新流程
    • 卸载流程

React是如何渲染组件的

我们就按照平时书写React代码的顺序来理清React把组件代码渲染到最终的真实DOM中的流程。

一般来讲,我们都会先定义一个组件。我们想要在页面中看到这个组件的渲染结果的化,就需要以JSX的形式将组件传入ReactDOM.render方法的第一个参数,我们要理解,这里的JSX经过React内部的转译,其实是一个React.createElement创建React元素的方法。render方法获取到React元素之后会将它实例化,之后它会根据实例化的React元素创建出真实的DOM元素,再根据第二个参数的指向,将创建好的元素插入到目标DOM容器当中。

为了加深理解,还是稍微来了解一下React本身运行流程。

在新版本的React当中,React的底层被重写了。React换上了一个新的引擎,这个引擎叫做React Fiber.React Fiber 作用的也即是React最核心的功能,它将React应用界面更新的过程分为了两个主要的部分:

  • 调度过程
  • 执行过程

前面还是一样,React对虚拟DOM进行Diff操作,对比应用前后改变的部分,之后进入调度阶段,React Fiber会计算出最优化的调度方法,以防止我们更新DOM是发生卡顿阻塞等状况,然后进入执行过程提交调度计算的结果,最终由ReactDOM负责将页面中的内容渲染到页面当中。

在调度过程中,有4个生命周期函数会被触发:

  • componentWillMount
  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate

在执行过程中,有3个生命周期函数会被触发:

  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

React组件生命周期方法

React为了方便我们更好地控制自己的应用,提供了许多预置的生命周期方法。就好像我们上面介绍的挂载流程,这些固定的生命周期方法分别会在组件的挂载流程、更新流程、卸载流程中触发。

假如有同学实在是不理解上一个部分介绍的渲染原理也没有关系,在学习下面的内容之前,我们只需要明确,React的初次render会进行挂载流程,挂载之后的后续渲染进行的都是更新流程,最后,我们也可以通过ReactDOM.unmountComponentAtNode方法将组件卸载,这其中进行的就是卸载流程。

如果只是一直讲解概念,感觉会很抽象不好理解,所以我们不如来亲眼看一看这三个流程是如何运行,如何触发生命周期函数的:

See the Pen React Counter logging lifecycle CN by Discountry (@discountry) on CodePen.dark

这里我们定义了两个组件,父组件会传递state到子组件当中,顺便state的变化和props的传递流程我们也可以在这个示例中看到。子组件的每个生命周期函数中我们都在控制台输出了相关的信息,这样在它被触发的时候,我们在控制台中就能够观察到(你现在就可以打开Chrome控制台试验一下)。

挂载流程

首先是组件初次渲染的挂载流程,我们通过一个按钮绑定渲染函数来手动触发组件的渲染。

初次挂载组件时,我们定义的组件类首先会被构造声明。在渲染之前会触发componentWillMount,渲染完成会触发componentDidMount这两个生命周期函数。

componentWillMount方法会在render之前被触发,只会在组件被渲染出来之前触发一次,如果你是使用ES6的Class声明组件的话,时机和作用几乎与constructor相同,在componentWillMount方法里对state定义或改变不会触发重新渲染。

componentDidMount在初次渲染完成之后被触发,也只会触发一次,在这个方法里你已经可以访问渲染出的DOM元素了。官方推荐在这个函数中进行一些例如ajax请求的操作,所以它也是我们最经常使用的生命周期函数。

更新流程

我们是在子组件中观察生命周期函数运行的,之前已经提到过了,这个示例中父组件会通过props向子组件传递state中的计数值,所以我们首先观察到的被触发的生命周期函数叫做componentWillReceiveProps这是在我们调用this.setState()方法之后,子组件收到新的props之后会触发的函数。值得提醒的一点是,正如这个函数命名中的Will,更新流程运行到这一步,组件的props还没有改变,我们可以通过nextProps获取到新的props,但如果我们试着查看this.props会发现它还是之前的值。

接下来被触发的shouldComponentUpdate是一个很特殊的函数,它默认会返回ture,但假如这个函数返回false的话,更新流程就会被跳过,render也不会继续被触发。假如你想要提升你应用的性能,可以在这个函数中自定义一些判断来跳过不需要被更新的组件。

然后通过实验我们也可以看到,组件在初次渲染流程或者使用forceUpdate方法时是不会触发这个函数的。

接下来的两个生命周期函数componentWillUpdate和componentDidUpdate分别会在render的前后被触发。

需要注意的是在componentWillUpdate无法调用this.setState()你可以理解为更新流程到这一步想要再更改state已经晚了。如果有需要你可以在之前的componentWillReceiveProps中更新state,React会把改变合并到一个更新流程里进行。

而componentDidUpdate则是另一个比较适合我们发起ajax请求的地方,在这个方法里我们还可以比较前后的props变化,再决定是否发起网络请求。一个比较实际的使用场景是保存用户输入到服务器,用户可能会来来回回修改输入的内容,但假如我们判断在修改前后数据最终没有改变,就没有必要发起不必要的网络请求了。

卸载流程

组件既然可以被挂载,同样也可以被卸载,我们可以通过一个名为ReactDOM.unmountComponentAtNode的方法来卸载已经挂载的React组件。在卸载流程中,名为componentWillUnmount的函数会被触发。在组件挂载之后,我们可能定义了一些计时器、绑定了事件监听函数等等,在卸载流程的生命周期函数中,也是我们解绑这些函数的合适位置。

实例

接下来呢,我们一起来实现一个小的例子,掌握一下生命周期函数的具体使用方法。

我们要写的是一个从Reddit异步获取帖子内容的小应用。

在这个示例当中,我们结合前面介绍的一些生命周期函数,演示了一下在应用当中具体应该如何使用,另外示例中我们还单独引入了一个用来发起请求的 axios 库,同理你用 $.ajax 或者原生的数据请求方法也是一样的。

See the Pen React Component state reddit list by Discountry (@discountry) on CodePen.dark

总结

  • React组件渲染包含三个流程:挂载流程、更新流程、卸载流程
  • 各个生命周期函数会在特定的时刻触发并适用于不同的使用场景
  • 通过使用生命周期函数我们可以对应用进行更精准的控制
  • 如果你需要发起网络请求,将其安排在合适的生命周期函数中是值得推荐的做法
  • 了解掌握React组件渲染的流程和原理对我们更深入掌握React非常有帮助

欢迎关注 余博伦 微信公众号