# 进化的生命周期方法:React 16 生命周期工作流详解

来源于:https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram (opens new window)

在React 16.4之后,React 生命周期在之前版本的基础上又经历了一次微调,其实就调在了更新过程的getDerivedStateFromProps 这个生命周期上

这里我先提供一个 Demo,它将辅助你理解新的生命周期。Demo 代码如下

import React from "react";
import ReactDOM from "react-dom";
// 定义子组件
class LifeCycle extends React.Component {
  constructor(props) {
    console.log("进入constructor");
    super(props);
    // state 可以在 constructor 里初始化
    this.state = { text: "子组件的文本" };
  }
  // 初始化/更新时调用
  static getDerivedStateFromProps(props, state) {
    console.log("getDerivedStateFromProps方法执行");
    return {
      fatherText: props.text
    }
  }
  // 初始化渲染时调用
  componentDidMount() {
    console.log("componentDidMount方法执行");
  }
  // 组件更新时调用
  shouldComponentUpdate(prevProps, nextState) {
    console.log("shouldComponentUpdate方法执行");
    return true;
  }

  // 组件更新时调用
  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log("getSnapshotBeforeUpdate方法执行");
    return "haha";
  }
  // 组件更新后调用
  componentDidUpdate(preProps, preState, valueFromSnapshot) {
    console.log("componentDidUpdate方法执行");
    console.log("从 getSnapshotBeforeUpdate 获取到的值是", valueFromSnapshot);
  }
  // 组件卸载时调用
  componentWillUnmount() {
    console.log("子组件的componentWillUnmount方法执行");
  }
  // 点击按钮,修改子组件文本内容的方法
  changeText = () => {
    this.setState({
      text: "修改后的子组件文本"
    });
  };
  render() {
    console.log("render方法执行");
    return (
      <div className="container">
        <button onClick={this.changeText} className="changeText">
          修改子组件文本内容
        </button>
        <p className="textContent">{this.state.text}</p>
        <p className="fatherContent">{this.props.text}</p>
      </div>
    );
  }
}
// 定义 LifeCycle 组件的父组件
class LifeCycleContainer extends React.Component {

  // state 也可以像这样用属性声明的形式初始化
  state = {
    text: "父组件的文本",
    hideChild: false
  };
  // 点击按钮,修改父组件文本的方法
  changeText = () => {
    this.setState({
      text: "修改后的父组件文本"
    });
  };
  // 点击按钮,隐藏(卸载)LifeCycle 组件的方法
  hideChild = () => {
    this.setState({
      hideChild: true
    });
  };
  render() {
    return (
      <div className="fatherContainer">
        <button onClick={this.changeText} className="changeText">
          修改父组件文本内容
        </button>
        <button onClick={this.hideChild} className="hideChild">
          隐藏子组件
        </button>
        {this.state.hideChild ? null : <LifeCycle text={this.state.text} />}
      </div>
    );
  }
}
ReactDOM.render(<LifeCycleContainer />, document.getElementById("root"));
@程序员poetry: 代码已经复制到剪贴板

React 16 以来的生命周期也可以按照“挂载”“更新”和“卸载”三个阶段来看,所以接下来我们要做的事情仍然是分阶段拆解工作流程。

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

为了凸显 16 和 15 两个版本生命周期之间的差异,我将两个流程绘制到了同一张大图里,请看下面这张图:

运行示例代码

消失的 componentWillMount,新增的 getDerivedStateFromProps

从上图中不难看出,React 15 生命周期和 React 16.3 生命周期在挂载阶段的主要差异在于废弃了 componentWillMount,新增了 getDerivedStateFromProps

注:细心的你可能记得,React 16 对 render 方法也进行了一些改进。React 16 之前,render方法必须返回单个元素,而 React 16 允许我们返回元素数组和字符串

getDerivedStateFromProps 不是 componentWillMount 的替代品

事实上,componentWillMount 的存在不仅“鸡肋”而且危险,因此它并不值得被“代替”,它就应该被废弃

  • getDerivedStateFromProps 这个 API,其设计的初衷不是试图替换掉 componentWillMount,而是试图替换componentWillReceiveProps,因此它有且仅有一个用途:使用 props 来派生/更新 state
  • React 团队为了确保 getDerivedStateFromProps 这个生命周期的纯洁性,直接从命名层面约束了它的用途(getDerivedStateFromProps 直译过来就是“从 Props 里派生 State”)。所以,如果你不是出于这个目的来使用 getDerivedStateFromProps,原则上来说都是不符合规范的

值得一提的是,getDerivedStateFromProps更新和挂载两个阶段都会“出镜”(这点不同于仅在更新阶段出现的 componentWillReceiveProps)。这是因为“派生 state”这种诉求不仅在 props 更新时存在,在 props 初始化的时候也是存在的

认识 getDerivedStateFromProps

这个新生命周期方法的调用规则如下:static getDerivedStateFromProps(props, state)

在使用层面,你需要把握三个重点。

第一个重点是最特别的一点getDerivedStateFromProps 是一个静态方法。静态方法不依赖组件实例而存在,因此你在这个方法内部是访问不到 this 的。若你偏要尝试这样做,必定报错,报错形式如下图所示:

第二个重点,该方法可以接收两个参数:props 和 state,它们分别代表当前组件接收到的来自父组件的 props 和当前组件自身的 state。我们可以尝试在 Demo 中输出这两个参数看一看,输出效果如下图所示:

可以看出,挂载阶段输出的 props 正是初始化阶段父组件传进来的 this.props 对象;而 stateLifeCycle 组件自身的 state 对象

第三个重点,getDerivedStateFromProps 需要一个对象格式的返回值。如果你没有指定这个返回值,那么大概率会被 React 警告一番,警告内容如下图所示:

getDerivedStateFromProps 的返回值之所以不可或缺,是因为 React 需要用这个返回值来更新(派生)组件的 state。因此当你确实不存在“使用 props 派生 state ”这个需求的时候,最好是直接省略掉这个生命周期方法的编写,否则一定记得给它 return 一个 null

注意,getDerivedStateFromProps 方法对 state 的更新动作并非“覆盖”式的更新,而是针对某个属性的定向更新。比如这里我们在 getDerivedStateFromProps 里返回的是这样一个对象,对象里面有一个 fatherText 属性用于表示“父组件赋予的文本”:

{
  fatherText: props.text
}
@程序员poetry: 代码已经复制到剪贴板

该对象并不会替换掉组件原始的这个 state

this.state = { text: "子组件的文本" };
@程序员poetry: 代码已经复制到剪贴板

而是仅仅针对 fatherText 这个属性作更新(这里原有的 state 里没有 fatherText,因此直接新增)。更新后,原有属性与新属性是共存的,如下图所示

# Updating 阶段:组件的更新

React 15 与 React 16.3 的更新流程对比如下图所示:

React 16.4 对生命周期流程进行了“微调”,其实就调在了更新过程的 getDerivedStateFromProps 这个生命周期上

React 16.4 的挂载和卸载流程都是与 React 16.3 保持一致的,差异在于更新流程上:

  • React 16.4 中,任何因素触发的组件更新流程(包括由 this.setStateforceUpdate 触发的更新流程)都会触发 getDerivedStateFromProps
  • 而在 v 16.3 版本时,只有父组件的更新会触发该生命周期

到这里,你已经对 getDerivedStateFromProps 相关的改变有了充分的了解。接下来,我们就基于这层了解,问出生命周期改变背后的第一个“Why”。

1. 改变背后的第一个“Why”:为什么要用 getDerivedStateFromProps 代替 componentWillReceiveProps?

对于 getDerivedStateFromProps 这个 API,React 官方曾经给出过这样的描述

componentDidUpdate 一起,这个新的生命周期涵盖过时componentWillReceiveProps 的所有用例

在这里,请你细细品味这句话,这句话里蕴含了下面两个关键信息:

  • getDerivedStateFromProps 是作为一个试图代替 componentWillReceiveProps 的 API 而出现的;
  • getDerivedStateFromProps 不能完全和 componentWillReceiveProps 画等号,其特性决定了我们曾经在 componentWillReceiveProps 里面做的事情,不能够百分百迁移到getDerivedStateFromProps 里。

接下来我们就展开说说这两点。

  • 关于 getDerivedStateFromProps 是如何代替 componentWillReceiveProps 的,在“挂载”环节已经讨论过:getDerivedStateFromProps 可以代替 componentWillReceiveProps 实现基于 props 派生 state
  • 至于它为何不能完全和 componentWillReceiveProps 画等号,则是因为它过于“专注”了。这一点,单单从getDerivedStateFromProps 这个 API 名字上也能够略窥一二。原则上来说,它能做且只能做这一件事

getDerivedStateFromProps 生命周期替代 componentWillReceiveProps 的背后,是 React 16 在强制推行“只用 getDerivedStateFromProps 来完成 props 到 state 的映射”这一最佳实践。确保生命周期函数的行为更加可控可预测,从根源上帮开发者避免不合理的编程方式,避免生命周期的滥用;同时,也是在为新的 Fiber 架构铺路

2. 消失的 componentWillUpdate 与新增的 getSnapshotBeforeUpdate

咱们先来看看 getSnapshotBeforeUpdate 是什么:

getSnapshotBeforeUpdate(prevProps, prevState) {
  // ...
}
@程序员poetry: 代码已经复制到剪贴板

这个方法和 getDerivedStateFromProps 颇有几分神似,它们都强调了“我需要一个返回值”这回事。区别在于 getSnapshotBeforeUpdate 的返回值会作为第三个参数给到 componentDidUpdate。它的执行时机是在 render 方法之后,真实 DOM 更新之前。在这个阶段里,我们可以同时获取到更新前的真实 DOM 和更新前后的 state&props 信息

尽管在实际工作中,需要用到这么多信息的场景并不多,但在对于实

阅读全文
Last Updated: 3/30/2025, 1:18:33 PM