详解 React 16 生命周期

1,280 阅读6分钟

上一篇文章详解 React15 生命周期中,我们从挂载、更新、卸载三个阶段来介绍 React 15 的生命周期。本篇我们也将同样从这三个阶段来介绍 React16 的生命周期并对比 React 15 和 React 16 两个版本的差异。

React 16 基于 React 16.3 版本。

挂载阶段(Mounting):组件的初始化渲染

从上图可以得知,在挂载阶段中,React 15 和 React 16.3 生命周期的差异是 componentWillMount 替换为 getDerivedStateFromProps。换句话说,废弃了 componentWillMount,新增了 getDerivedStateFromProps

认识 getDerivedStateFromProps

该生命周期的调用方式:

static getDerivedStateFromProps(props, state)

该生命周期会在调用 render 方法之前调用,并且在初始挂载和后续更新时都会被调用。

示例如下:

import React from "react";

export default class App extends React.Component {
  static getDerivedStateFromProps(state, props) {
    console.log('getDerivedStateFromProps()');
  }

  render() {
    console.log('render()');
    return (
      <div>
        <h1>Hello World</h1>
      </div>
    );
  }
}

在使用上,有三点需要注意。

第一点,getDerivedStateFromProps 是个静态方法。静态方法不依赖组件实例而存在,所以此方法内部是获取不到 this 的。 如若强行获取 this 则会报错,如下图所示:

第二点,该方法可以接受两个参数:props 和 state。它们分别代表来自父组件的 props 和自身的 state。

第三点,该方法应返回一个对象来更新 state,如果返回 null 则不更新任何内容。若不返回任何内容,React 将会给出警告,如下图所示:

该方法对 state 的更新和 setState 一样,不是“覆盖式”的更新,是针对某个属性的定向更新。

修改我们的示例代码如下:

import React from "react";

export default class App extends React.Component {
    state = {
        message: "Hello World",
    };
    static getDerivedStateFromProps(state, props) {
        console.log("getDerivedStateFromProps()");
        return {
            name: "chen",
        };
    }
    componentDidMount() {
        console.log("state", this.state);
    }

    render() {
        console.log("render()");
        return (
            <div>
                <h1>Hello World</h1>
            </div>
        );
    }
}

输出如下:

关于 getDerivedStateFromProps() 的更多介绍请访问官网文档

更新阶段(Updating):组件的更新

在此阶段也新增了一个生命周期:getSnapshotBeforeUpdate

认识 getSnapshotBeforeUpdate

该生命周期的调用方式:

getSnapshotBeforeUpdate(prevProps, prevState)

该生命周期在最近一次渲染输出(提交到 DOM 节点)之前调用,即在 render 之后,componentDidUpdata 之前。它可以让我们获取到组件更新前的真实 DOM 和 更新前后的 state&props 信息。

值得注意的一点是,getSnapshotBeforeUpdate 的返回值会作为 componentDidUpdate 的第三个参数。

componentDidUpdate(prevProps, prevState, snapShot)

关于 getSanpshotBeforeUpdate() 的示例和更多介绍请访问官网文档

卸载阶段(Unmounting):组件的卸载

React 16.3 的卸载阶段与 React 15 相同,这里就不再赘述。

过时和新增的生命周期

由上述三个阶段的对比,我们可以知道 React 16.3 对比 React 15 版本哪些生命周期过时,又新增了哪些生命周期。

过时的生命周期

过时的组件生命周期往往会带来不安全的编码实践,具体生命周期如下:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

这些生命周期方法经常被误解和滥用,另外,它们在异步渲染中的危害或许会更大。因此 React 官方为这些生命周期添加了 “UNSAFE_” 前缀。(此处的 “unsafe” 不是指安全性,而是表示这些生命周期在 React 的未来版本中更有可能出现 bug,特别是在启用异步渲染之后)

新增的生命周期

  • getDerivedStateFromProps
  • getSnapshotBeforeUpdate

getDerivedStateFromProps 与 componentDidUpdate 一起,可以覆盖过时的 componentWillReceiveProps 的所有用例。

getSnapshotBeforeUpdate 与 componentDidUpdate 一起,可以覆盖过时的 componentWilUpdate 的所有用例。

React 16 为什么更改生命周期?

Fiber 架构简析

Fiber 是 React 16 对 React 核心算法的一次重写。关于 Fiber,我们现在只需知道:Fiber 会使 React 15 的递归的无法中断的更新变为异步的可中断更新

在具体介绍两者的区别前,先看下两张图:

React 15:

React 16:

在 React 16 之前,更新由于递归执行,所以一旦开始,中途就无法中断。当层级很深时,渲染时间会很长,页面就会面临卡顿甚至卡死的风险,影响用户体验。

而React 16 引入的 Fiber 架构就是解决这个问题的。在更新过程中,不再“一去不回头”,而是可以被打断的,这就是所谓的异步渲染

换个角度看生命周期

Fiber 架构的重要特征是可以中断异步渲染模式。根据是否可以中断,React 16 的生命周期被划分为了 render 和 commit 两个阶段,而 commit 阶段又被细分为 pre-commit 和 commit。各个阶段所包含的生命周期如下图所示:

三个阶段的特征:

  • render 阶段:纯净且不包含副作用,可能会被 React 暂停、中止或重新启动。
  • pre-commit 阶段:可以读取DOM。
  • commit 阶段:可以使用 DOM,运行副作用,安排更新。

总的来说,render 阶段在执行过程中可以中断,而 commit 阶段则总是同步执行的

render 阶段可以被 React 暂停、中止或重新启动,所以此阶段的生命周期都是有可能会被重复执行的。

或许大家会发现,上文我们提到的过时生命周期,都是在 render 阶段中的,因此这些生命周期都是可能重复执行的。而我们又很喜欢在这些以“componentWill”开头的生命周期中书写很多“骚操作”,包括但不限于:

  • setState()
  • 发起异步请求
  • 操作真实 DOM

这些操作在异步渲染中更有可能会导致重大 bug,所以 React 官方将这些生命置为 “UNSAFE_”的原因。

总结

本篇文章我们从三个阶段对比了 React 15 和 React 16.3 的区别,找出了过时和新增的生命周期并介绍了新增生命周期的用法。

我们又从架构方面分析 React 15 到 React 16.3 的改变,并对 React 生命周期的变化和背后的原因有了新角度的理解。

总的来说,React 16 改变生命周期的主要原因是为了配合 Fiber 架构带来的异步渲染机制。在 React 新版本中,针对生命周期中长期被滥用和误解的部分进行了强制性的最佳实践,确保了Fiber 架构下数据和视图的安全性和正确性,同时也确保了生命周期方法的行为更加纯碎、可控、可预测。(摘自《深入浅出搞定 React》)

参考