React组件的生命周期

前言

这是会不断更新的一篇文章,因为随着对生命周期的理解的不断深入,势必要加上很多东西。

一、组件生命周期的三个过程

了解React生命周期,对我们理解React的工作过程很有帮助。所有的事物都有自己的生命过程,React组件也不例外,同样有着自己的生命周期,通常而言它的生命会经历如下的三个过程:

  • 挂载过程
  • 更新过程
  • 销毁过程

总的过程如下:

生命周期
转自:https://zhuanlan.zhihu.com/p/32387027

在经历上面三个过程的时候,React库会一次调用组件的一些成员函数,这些函数称之为生命周期函数。因此,要定制一个react组件,实际上就是要定制这些生命周期函数。

二、挂载过程

在挂载过程中,当组件第一次被渲染时,会依次调用以下函数:

  • constructor
  • componentWillMount
  • render
  • componentDidMount

constructor

constructor也就是ES6语法中每个类的构造函数,而要创建一个组件类的实例,当然要调用对应的构造函数。但值得注意的是,不是每个React组件都需要定义自己的构造函数,使用构造函数一般都是以下两个原因:

  1. 初始化state。因为组件中的任何函数都可能需要访问state,因此在constructor中来初始化state是很好的选择。
  2. 绑定成员函数的this。在es6中,累的每个成员函数在执行时的this并不是和类的实例所绑定的。而在钩子凹函数中,this就是当前组件实例。所以,在此绑定this可以方便未来的调用。

componentWillMount

在挂载过程中,componentWillMount会在render函数之前被调用。但通常来讲我们不需要来定义componentWillMount,我们可以认为它所需要做的事情我们一般都在constructor中来做了。它所存在的意义更多的是为了与我们后面会提到的componentDidMount对称。但也有一些组件启动的动作,包括像 Ajax 数据的拉取操作、一些定时器的启动等,就可以放在 componentWillMount 里面进行。

1
2
3
4
5
componentWillMount () {
ajax.get('http://json-api.com/user', (userData) => {
this.setState({ userData })
})
}

render

rende 函数是 React 组件中最重要的函数。它并不是直接去渲染动作,而是返回一个 JSX 描述的结构,然后最终由 React 来渲染过程。

对于render函数需要注意以下两点:

  • 首先是我们在定义React组件的时候,可以忽略其他所有组件都不实现,但一定要实现render,因为所有React组件的父类对除了render函数之外的生命周期函数都有默认实现。
  • 其次render函数应该是一个纯函数,应该完全根据this.state和this.props来决定返回的结果,而不应该产生任何副作用。因此,不应该在render中调用this.setState,这会引起状态的变化。

另外还有一点需要注意,就是无论是挂载过程还是更新过程,都是以 render 函数为边界。在 render 之前的生命周期函数并没有真正的更新,而是只是作为一个参数,只有当 render 函数执行后,组件内部的 props 和 state 才会发生改变。这符合 React16 的 Fiber 的思路,因为Fiber中一个组件在执行时不一定会一次性的执行完,因此在没有渲染完成前可能会将 render 之前的生命周期函数执行多次,当这种情况发生时,如果 render 之前的生命周期函数会影响到 props 和 state,那么就会产生很多意想不到的副作用。

componentDidMount

在 render 函数调用完之后 componentDidMount 函数并不会马上被调用。它会在 render 函数返回的东西已经引发了渲染,组件已经被挂载到DOM树上时才会被调用。

前面提到的 render 函数知识返回 JSX,并不负责直接的渲染,因此 React 库需要把所有组件返回的结果全部集合起来,才可以知道如何产生对应的 DOM 修改。因此才会需要 componentDidMount 函数在 render 返回 JSX,组件被渲染了才会调用来收尾。

它还有一个与 componentWillMount 函数不同的是,它只能在浏览器端被使用,而 componentWillMount 则可以使用在服务器端与浏览器端。这是由于只有浏览器端才会发生渲染的原因,服务器端可不会发生组件的挂载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Header extends Component {
constructor () {
super()
console.log('construct');
}

componentWillMount () {
console.log('component will mount');
}

componentDidMount () {
console.log('component did mount');
}

render () {
console.log('render');
return (
<div>
<h1 className='title'>我是Srtian</h1>
</div>
)
}

construct index.js:15
component will mount index.js:19
render index.js:27
component did mount index.js:23

三、更新过程

当挂载过程结束之后,用户们便可以看见我们的页面了。但出于用户体验的角度来看,我们还需要提供更好的交互体验,我们就需要组件可以随着用户操作来改变页面内容,而 props 和 state 的修改,组件就会引发生更新过程。
更新过程包括以下生命周期函数:

  • componentWillReceiveProps:组件从父组件接收到新的 props 之前调用。
  • shouldComponentUpdate(nextProps, nextState):你可以通过这个方法控制组件是否重新渲染。如果返回 false 组件就不会重新渲染。这个生命周期在 React.js 性能优化上非常有用。
  • componentWillUpdate:组件开始重新渲染之前调用。
  • componentDidUpdate:组件重新渲染并且把更改变更到真实的 DOM 以后调用。

componentWillReceiveProps

值得注意的是,更新过程有两种方式一种是外部的父组件渲染,子组件也会进行更新,另一种是组件内部的 state 更新。这两个更新不同的是在内部的 state 更新所引发的更新过程,生命周期函数 componentwillReceiveProps 不会被调用,子组件收到新的 props 之后会触发的函数。通过它的名字其实我们就可已看出,它只是will,而不会去更改 props 的值。

有人在网上说,这个函数只有在组件的 props 函数发生改变时才会调用这个函数。其实不然,实际上,只要父组件的 render 函数被调用,在render 函数里面被渲染的子组件都会经历更新过程,componentWillReceiveProps 函数也就会触发。因此当我们不希望因为父组件的render 函数调用就引起子组件更新时,就需要使用下面这个 shouldComponentUpdate 来进行判断,是否需要进行更新过程。

而且,通过 this.setState 方法出发的更新过程也不会触发该函数。这是由于这个函数适合更具新得 props 值来计算出是不是要更新内部状态的 state。而 this.setState 就是用于更新组件的内部状态的,这会引起死循环。

shouldComponentUpdate

shouldComponentUpdate 是一个很特殊的函数,它决定了一个组件什么时候不需要渲染。它是除了 render 函数之外最重要的函数了。

他们两个也是 React 生命周期函数中唯二要求返回结果的函数。其中 render 函数的返回结果用于构造DOM对象,而它会返回同一个布尔值,默认会返回 ture,但假如这个函数返回 false 的话,更新流程就会被跳过,render 也不会继续被触发。

说 shouldComponentUpdate 重要就是由于只要利用的好,我们可以在这个函数中自定义一些判断来跳过不需要被更新的组件,从而提升性能。

componentWillUpdate和componentDidUpdate

componentWillUpdate 和 componentDidUpdate 分别会在 render 的前后被触发。

componentWillUpdate

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

componentDidUpdate

componentDidUpdate 则是另一个比较适合我们发起 ajax 请求的地方,在这个方法里我们还可以比较前后的props变化,再决定是否发起网络请求。一个比较实际的使用场景是保存用户输入到服务器,用户可能会来来回回修改输入的内容,但假如我们判断在修改前后数据最终没有改变,就没有必要发起不必要的网络请求了。我们也可以通过它来来调用其他的UI库。而且他也可以在服务器端来调用,因为服务器端中使用 React 基本不会经历更新过程,所有也就无所谓了。

四、卸载过程

componentWillUnmount

在卸载流程中,名为 componentWillUnmount 的函数会被触发。在组件挂载之后,我们可能定义了一些计时器、绑定了事件监听函数等等,在卸载流程的生命周期函数中,也是我们解绑这些函数的合适位置。

它的工作也一般和 componentDidMount 有关。比如,在 componentDidMount 中用非 React 的方法创造一些DOM元素,如果撒手不管就可能造成内存泄漏,这就需要 componentWillUnmount 函数来吧这些创造的DOM元素清理掉。

五、新增的生命周期函数

componentDidCatch

1
componentDidCatch(error, info)

这是 React 16 新加入的一个生命周期函数。定义该生命周期函数的组件将会成为一个错误边界,错误边界这个词非常形象,它可以有效地将错误限制在一个有限的范围内,而不会导致整个应用崩溃,防止一颗耗子屎坏了一锅汤。

错误边界组件,可以捕获其整个子组件树内发生的任何异常,但是却不能捕获自身的异常