生命周期函数

一个人从出生到死亡会经历一些人生节点,孩童、青年、中年、老年。而一个组件从创建到销毁也要经历特殊的节点,在这些节点中,我们可以做一些操作,每一个节点都对应一个函数,我们将它们称为生命周期函数。

那React有哪些生命周期函数,这些生命周期函数对应的节点是什么呢? 或者说这些生命周期函数在React从创建到销毁的哪一个过程会被执行呢? 这里我们将生命周期分为三个阶段:

  • 创建阶段
  • 更新阶段
  • 卸载阶段

在每一个阶段都包含数个生命周期函数。

首先来看创建阶段的生命周期函数:

  1. constructor(props)

我们一般在构造函数中干两件事情:

  • 初始化状态
  • 为事件处理函数绑定this

注意: ES6子类的构造函数必须执行一次super()。React如果构造函数中要使用this.props,必须先执行super(props)。

  1. static getDerivedStateFromProps(nextProps, prevState)

当创建时、接收新的 props 时、setState时、forceUpdate时会执行这个方法。这是一个静态方法,参数nextProps是新接收的props,prevState是当前的state。返回值(对象)将用于更新state,如果不需要更新则需要返回null。

这个方法在建议尽量少用,只在必要的场景中使用,一般使用场景如下:

  • 无条件的根据props更新state
  • 当props和state的不匹配情况更新state
  1. componentWillMount()

这个方法已经不推荐使用。因为在未来异步渲染机制下,该方法可能会多次调用。它所行使的功能也可以由 componentDidMount() 和 constructor() 代替:

  • 之前有些人会把异步请求放在这个生命周期,其实大部分情况下都推荐把异步数据请求放在 componentDidMount() 中。
  • 在服务端渲染时,通常使用 componentWillMount() 获取必要的同步数据,但是可以使用 constructor() 代替它。

有定义getDerivedStateFromProps时,会忽略componentWillMount()

  1. render()

每个类组件中,render() 唯一必须的方法。render() 正如其名,作为渲染用,可以返回下面几种类型:

  • React 元素(React elements)
  • 数组(Arrays)
  • 片段(fragments)
  • 插槽(Portals)
  • 字符串或数字(String and numbers)
  • 布尔值或 null(Booleans or null)

里面不应该包含副作用,应该作为纯函数。不能使用 setState。

  1. componentDidMount()

组件完成装载(已经插入 DOM 树)时,触发该方法。这个阶段已经获取到真实的 DOM。一般用于下面的场景:

  • 异步请求 ajax
  • 添加事件绑定(注意在componentWillUnmount中取消,以免造成内存泄漏)

更新阶段:

  1. componentWillReceiveProps()

这个方法在接收新的 props 时触发,即使 props 没有变化也会触发。一般用这个方法来判断 props 的前后变化来更新 state,如下面的例子:

  1. class ExampleComponent extends React.Component {
  2. state = {
  3. isScrollingDown: false,
  4. };
  5. componentWillReceiveProps(nextProps) {
  6. if (this.props.currentRow !== nextProps.currentRow) {
  7. this.setState({
  8. isScrollingDown:
  9. nextProps.currentRow > this.props.currentRow,
  10. });
  11. }
  12. }
  13. }

这个方法将被弃用,推荐使用 getDerivedStateFromProps 代替。

  1. static getDerivedStateFromProps()

见创建阶段的描述

  1. shouldComponentUpdate()

在接收新的 props 或新的 state 时,在渲染前会触发该方法。该方法通过返回 true 或者 false 来确定是否需要触发新的渲染。返回 false, 则不会触发后续的 componentWillUpdate()、render() 和 componentDidUpdate()(但是 state 变化还是可能引起子组件重新渲染)。

所以通常通过这个方法对 props 和 state 做比较,从而避免一些不必要的渲染。

  1. componentWillUpdate()

当接收到新的 props 或 state 时,在渲染前执行该方法。在以后异步渲染时,可能会出现某些组件暂缓更新,导致 componentWillUpdate 和 componentDidUpdate 之间的时间变长,这个过程中可能发生一些变化,比如用户行为导致 DOM 发生了新的变化,这时在 componentWillUpdate 获取的信息可能就不可靠了。这个方法将要弃用。

  1. render()

见创建阶段的描述

  1. getSnapshotBeforeUpdate()

这个方法在 render() 之后,componentDidUpdate() 之前调用。两个参数 prevProps 表示更新前的 props,prevState 表示更新前的 state。返回值称为一个快照(snapshot),如果不需要 snapshot,则必须显示的返回 null —— 因为返回值将作为 componentDidUpdate() 的第三个参数使用。所以这个函数必须要配合 componentDidUpdate() 一起使用。

这个函数的作用是在真实 DOM 更新(componentDidUpdate)前,获取一些需要的信息(类似快照功能),然后作为参数传给 componentDidUpdate。例如:在 getSnapShotBeforeUpdate 中获取滚动位置,然后作为参数传给 componentDidUpdate,就可以直接在渲染真实的 DOM 时就滚动到需要的位置。

下面是官方文档给出的例子:

  1. class ScrollingList extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. this.listRef = React.createRef();
  5. }
  6. getSnapshotBeforeUpdate(prevProps, prevState) {
  7. // Are we adding new items to the list?
  8. // Capture the scroll position so we can adjust scroll later.
  9. if (prevProps.list.length < this.props.list.length) {
  10. const list = this.listRef.current;
  11. return list.scrollHeight - list.scrollTop;
  12. }
  13. return null;
  14. }
  15. componentDidUpdate(prevProps, prevState, snapshot) {
  16. // If we have a snapshot value, we've just added new items.
  17. // Adjust scroll so these new items don't push the old ones out of view.
  18. // (snapshot here is the value returned from getSnapshotBeforeUpdate)
  19. if (snapshot !== null) {
  20. const list = this.listRef.current;
  21. list.scrollTop = list.scrollHeight - snapshot;
  22. }
  23. }
  24. render() {
  25. return (
  26. <div ref={this.listRef}>{/* ...contents... */}</div>
  27. );
  28. }
  29. }
  1. componentDidUpdate()

这个方法是在更新完成之后调用,第三个参数 snapshot 就是 getSnapshotBeforeUpdate 的返回值。

正如前面所说,有 getSnapshotBeforeUpdate 时,必须要有 componentDidUpdate。所以这个方法的一个应用场景就是上面看到的例子,配合 getSnapshotBeforeUpdate 使用。

卸载阶段:

  1. componentWillUnmount()

在组件卸载或者销毁前调用。这个方法主要用来做一些清理工作,例如:

  • 取消定时器
  • 取消事件绑定
  • 取消网络请求

上面三个阶段是正常的生命周期,但是如果发生了异常,就需要进行错误处理,所以React也提供了发生异常时的函数

  1. componentDidCatch(err, info)

任何子组件在渲染期间,生命周期方法中或者构造函数 constructor 发生错误时调用。

错误边界不会捕获下面的错误:

  • 事件处理(Event handlers)(因为事件处理不发生在 React 渲染时,报错不影响渲染)
  • 异步代码(Asynchronous code)(e.g. setTimeout or requestAnimationFrame callbacks)
  • 服务端渲染(Server side rendering)
  • 错误边界本身(而不是子组件)抛出的错误

下面我们来看一张生命周期函数图来加深对上面描述的生命周期函数的理解
生命周期函数 - 图1
虽然React有做向下兼容,但是推荐尽量避免使用废弃的生命周期,而是拥抱未来,用新的生命周期替换它们。