学习笔记:关于React的生命周期(V17)

188 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第6天,点击查看活动详情

最近刚刚开始学React框架了,学习Vue时我们了解了Vue的生命周期,毫不例外React也有自己的生命周期啦。 要完全将一个框架融汇贯通,知道如何运用生命周期钩子是非常重要的。

让我们用一幅图来理解生命周期

image.png

乍一看这幅图感觉非常难理解,你会觉得为什么和VUE完全不一样,不是明明白白清清楚楚的流程图类型??这看不太明白啊,别着急,让我们通过代码加文档一一来拆解~

辅助页面准备

我们先准备一个这样的React页面,页面上准备三个按钮和一个变量

按钮1:控制变量加1

按钮2:卸载组件

按钮3:强制更新视图而不更新数据

image.png 以下附上代码,生命周期钩子已经配好了,先别管看不看得懂在干嘛,这只是为了更好的帮我们理解生命周期而已

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script
      src="https://unpkg.com/react@17/umd/react.development.js"
      crossorigin
    ></script>
    <script
      src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"
      crossorigin
    ></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
    <title>Document</title>
  </head>
  <body>
    <div id="root"></div>

    <script type="text/babel">
      class Count extends React.Component {
        constructor(props) {
          console.log("constructor");
          super(props);
          this.state = {
            count: 0,
          };
        }

        add = () => {
          const { count } = this.state;
          this.setState({ count: count + 1 });
        };
        death = () => {
          ReactDOM.unmountComponentAtNode(document.getElementById("root"));
        };
        force = () => {
          this.forceUpdate();
        };

        // 得到一个额外的状态
        static getDerivedStateFromProps(props, state) {
          console.log("getDerivedStateFromProps", props, state);
          return null;
        }
        // 组件挂载完毕
        componentDidMount() {
          console.log("componentDidMount");
        }
        // 控制组件更新的‘阀门’
        shouldComponentUpdate() {
          console.log("shouldComponentUpdate");
          return true;
        }

        // 更新之前获取快照
        getSnapshotBeforeUpdate() {
          console.log("getSnapshotBeforeUpdate");
          return "hello";
        }
        // 组件更新完毕
        componentDidUpdate(preProps, preState, snapshotValue) {
          console.log("componentDidUpdate", preProps, preState, snapshotValue);
        }

        // 组件即将卸载
        componentWillUnmount() {
          console.log("componentWillUnmount");
        }

        render() {
          console.log("render");
          const { count } = this.state;
          return (
            <div>
              <h2>当前求和为:{count}</h2>
              <button onClick={this.add}>点我+1</button>
              <button onClick={this.death}>卸载组件</button>
              <button onClick={this.force}>
                不更改任何状态数据,强制更新一下
              </button>
            </div>
          );
        }
      }
      ReactDOM.render(<Count count={100} />, document.getElementById("root"));
    </script>
  </body>
</html>

我们暂且将生命周期拆分为三个大阶段,分别是创建时、更新时、卸载时,没问题吧,我们vue也有这3个大类生命周期。


创建时

1. constructor

在 React 组件挂载之前,会调用它的构造函数。在为 React.Component 子类实现构造函数时,应在其他语句之前调用 super(props)。否则,this.props 在构造函数中可能会出现未定义的 bug。

什么意思呢?意思就是我们每次在创建一个子组件时都会自动调用构造函数。当然也可以不写,我们学过构造类就知道,如果不显式定义的话,也会自动生成一个内部为空的默认的constructor(){}方法。

问题来了,什么时候需要显示定义呢??

  • 通过给 this.state 赋值对象来初始化内部 state。
  • 为事件处理函数绑定实例。

如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。

举个例子

constructor(props) {
  super(props);
  this.state = { counter: 0 };///初始化内部 state。
  this.handleClick = this.handleClick.bind(this);//为事件处理函数绑定实例
}

那这里有一个super()超级继承,这个关键字是用来干什么的呢

class方法中,通过extends关键字来继承父类,就必须在constructor()方法中调用super() 方法,否则新建实例时会报错。

报错的原因是:子类没有自己的this对象的,它只能继承父类this对象,然后对其进行加工,而super( )就是将父类中的this对象继承给子类。没有super,子类就得不到this对象。

又有人会问了props是用来干嘛的呢?就是和vue里面的props一样啊,接收父组件传过来的参数啊

上面那段代码,我们在渲染子组件的时候也传了一个count数据给子组件呀,所以接收了一下

image.png


2. static getDerivedStateFromProps(props, state)

getDerivedStateFromProps 会在调用render方法之前调用,即在渲染 DOM 元素之前会调用,并且在初始挂载及后续更新时都会被调用。

接收两个参数props和state

getDerivedStateFromProps 的存在只有一个目的:state数据更新依赖于props


3. render 渲染结构

当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一:

  • React 元素。通常通过 JSX 创建。例如,<div/> 会被 React 渲染为 DOM 节点,<MyComponent />会被 React 渲染为自定义组件,无论是<div /> 还是<MyComponent /> 均为 React 元素。
  • 数组或 fragments 使得 render 方法可以返回多个元素.
  • Portals 可以渲染子节点到不同的 DOM 子树中。
  • 字符串或数值类型 它们在 DOM 中会被渲染为文本节点。
  • 布尔类型或 null 什么都不渲染。(主要用于支持返回 test && <Child /> 的模式,其中 test 为布尔类型。)

render() 函数应该为纯函数,这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。

这个是我们必须要用的,所以大家应该很清楚什么用法。

4. componentDidMount

组件渲染完成之后,执行componentDidMount,代表着组件挂载完毕了,一般我们会选择在这个钩子中发请求,类似于vue中的mounted()

以上就是组件渲染时的生命周期的钩子了,我们打开之前准备好的页面,我已经在页面中将所有的钩子都写入了,并每个钩子有自身对应的输出,我们看看组件渲染时的顺序是不是这样

image.png


更新时

1. static getDerivedStateFromProps(props, state)

这里和创建视图时的getDerivedStateFromProps内部逻辑一模一样,不再做过多赘述

2. shouldComponentUpdate(nextProps, nextState)

关于这个API是选择性使用的,在Vue中我们更新数据会立即更新视图,无法人为干扰视图的更新,但是React中给我们提供了这样一个方法,让我们可以控制选择是否选择更新视图。

它默认接收两个参数nextProps, nextState

函数需要返回一个布尔值,根据 shouldComponentUpdate() 的返回值,truefalse,判断 React 组件的视图更新是否受当前 state 或 props 更改的影响。

若为false更新到这一步就结束了,默认值为ture,走以下的生命周期钩子


3. render()(shouldComponentUpdate返回值为true的前提条件下)

这里和创建视图时的render内部逻辑一模一样,不再做过多赘述


4. getSnapshotBeforeUpdate()

在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。

此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()。

更新之前获取 DOM 快照等同于截图,获取一些 DOM 信息,可以在这一步将旧DOM和新DOM进行对比,对新DOM做一些处理。

必须有 return 值,return 的值将作为snapshotVal传给componentDidUpdate。个人认为它这里和虚拟DOM有一些异曲同工之妙,主要是为了用来节省渲染更新后的组件时性能。


5. componentDidUpdate(preProps,preState,snapshotVal)

componentDidUpdate() 会在数据更新后会被立即被触发,它默认接收三个参数prevProps, prevState, snapshot 可以在这里做一些前后DOM数据的对比,也可以选择在此处进行网络请求。关于使用官方是这样写的。

可以在里面直接调用 setState() ,但请注意它必须被包裹在一个条件语句里,否则会导致死循环。它还会导致额外的重新渲染,虽然用户不可见,但会影响组件性能。不要将 props “镜像”给 state,请考虑直接使用 props。

更新到这一步就完毕了

我们再回到我们准备的页面中,看看如果点击按钮1,让count+1会是不是触发了这些钩子的顺序

image.png


额外的生命周期:forceUpdate(callback) 强制刷新

默认情况下,当组件的 state 或 props 发生变化时,组件将重新渲染。但如果 render() 方法依赖于其他数据,则可以调用 forceUpdate() 强制让组件重新渲染。

调用 forceUpdate() 将致使组件调用 render() 方法,此操作会跳过该组件的 shouldComponentUpdate()。但其子组件会触发正常的生命周期方法,包括 shouldComponentUpdate() 方法。

我们再点击页面中第三个按钮不更改任何数据状态强制更新一下看看

image.png

果然触发了正常的生命周期,且跳过了shouldComponentUpdate()


卸载时

componentWillUnmount(callback)

卸载这一步就只有一个钩子,它和vue的onBeforeUnmount()是一样的,主要是用来处理一些善后工作。

我们的React是单页面应用,那么在组件转跳的时候,当前组件就会被销毁。那如果我们这个页面中存在一些类似于定时器一样的异步调用的功能方法呢,那它将一直存在在这个页面上,造成空间和性能的浪费,所以我们在这一步就是要把页面上的定时器等任务清除掉。

image.png