React 生命周期详细梳理

1,663 阅读7分钟

React 生命周期.png

一、什么是生命周期函数?

事物都有从创建到销毁的整个过程,这个过程称之为是生命周期

React 组件也有自己的生命周期,了解组件的生命周期可以让我们在最合适的地方完成自己想要的功能;

二、生命周期和生命周期函数的关系

生命周期是一个抽象的概念,在生命周期的整个过程,分成了很多个阶段。比如:人的少年、中年、老年

React 内部为了告诉我们当前处于哪些阶段,会对我们组件内部实现的某些函数进行回调,这些函数会在生命周期中某一个时刻组件会自动调用执行,这些函数就是生命周期函数

注意: 函数式组件没有生命周期函数,但可以通过 useEffect(React v16.8) 模拟生命周期函数

三、React 16 前的生命周期函数

React 16 前的生命周期函数

1. 组件初始化(Initialization)阶段

首先是Initialization,初始化state和props的数据,也就是类的构造方法 constructor(), Test 类继承了 React.Component 这个基类,也就继承这个 React 的基类,才能有 render() ,生命周期等方法可以使用,这也说明为什么 函数组件不能使用这些方法的原因

constructor中通常只做两件事情:

  1. 通过给 this.state 赋值对象来初始化内部的state;

  2. 为事件绑定实例(this);

class Test extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      first: 1
    };
    this.handleClick = this.handleClick.bind(this);
  }
}

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

import { Component } from "react";
import "./App.css";

function App() {
  return <Test content="hello"></Test>;
}

class Test extends Component {
  render() {
    return <span>{this.props.content}</span>;
  }
}

export default App;

在上面代码中我并没有初始化 this.props,但是还可以正常调用,那是因为在 React 源码中,帮助我们初始化了 this.props

react源码Component

2. 组件的挂载(Mounting)阶段

  • componentWillMount
  • render
  • componentDidMount

2.1 componentWillMount

组件即将被挂载到页面的时刻自动执行

React v16 弃用

2.2 render

根据组件的props 和 state,return 一个 React 元素(描述组件,即UI),不负责组件实际渲染工作,之后由 React 自身根据此元素去渲染出页面 DOM。render 是纯函数(Pure function:函数的返回结果只依赖于它的参数;函数执行过程里面没有副作用),不能在里面执行this.setState,会有改变组件状态的副作用。

2.3 componentDidMount

组件挂载后(插入 DOM 树中)立即调用

常用操作:

  1. 依赖于DOM的操作可以在这里进行;
  2. 在此处发送网络请求就最好的地方(官方建议);
  3. 可以在此处添加一些订阅(会在componentWillUnmount取消订阅);

3. 组件的更新(Updation)阶段

  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate
  • render(这里只是重新调用)
  • componentDidUpdate

在讲述此阶段前需要先明确下 react 组件更新机制。setState引起的 state 更新或父组件重新 render 引起的 props 更 新,更新后的 state 和 props 相对之前无论是否有变化,都将引起子组件的重新 render。

3.1 造成组件更新情况

1. 每当父组件重新 render 导致的重传 props,子组件将直接跟着重新渲染,无论 props 是否有变化。

可通过 shouldComponentUpdate,或者继承 PureComponent 类方法优化,函数式组件可以使用 memo() 包裹

class Child extends Component { 
  shouldComponentUpdate(nextProps) { 
    // 应该使用这个方法,否则无论props是否有变化都将会导致组件 跟着重新渲染
    if(nextProps.someThings === this.props.someThings) {
        return false
    }
  }
  render() { 
    return <div>{this.props.someThings}</div> 
  }
}
2. 组件本身调用 setState,无论 state 有没有变化。

可通过 shouldComponentUpdate,或者继承 PureComponent 类方法优化,函数式组件可以使用 memo() 包裹

class Child extends Component {
  constructor(props) {
    super(props);
    this.state = { someThings: 10 };
  }
  shouldComponentUpdate(nextProps, nextState) {
    // 应该使用这个方法,否则无论state是否有变化都将会导致组件重新渲染
    if (nextState.someThings === this.state.someThings) {
      return false;
    }
    return true;
  }
  handleClick = () => {
    // 虽然调用了setState ,但state并无变化
    const preSomeThings = this.state.someThings;
    this.setState({ someThings: preSomeThings });
  };
  render() {
    return <div onClick={this.handleClick}>{this.state.someThings}</div>;
  }
}

3.2 shouldComponentUpdate(nextProps, nextState)

组件被更新之前,它会自动被执行,判断组件是否需要更新,返回布尔值

// nextProps 更新后的 props 的值
// nexState 更新后的 state 的值
shouldComponentUpdate(nextProps, nextState) {
	// nextState.someThings 更新后的数据
    	// this.state.someThings 当前数据
    	// 判断前后数据是否相同
	if (nextState.someThings === this.state.someThings) {
      		// 不更新
      		return false;
    	}
    	// 更新
  	return true;
}

注意: shouldComponentUpdate 内部不推荐使用深比较,因为使用 shouldComponentUpdate 比较的目的是为了减少 render 的执行,render 执行会创建虚拟 DOM,新老 DOM 进行比较(diff 算法),从而提高性能,但是深比较的时间可能会比 diff 算法的时间更长,所以不推荐使用深比较

3.3 componentWillUpdate(nextProps, nextState)

组件被更新之前,如果shouldComponentUpdate 返回 true 它才执行,如果返回 false , 不会执行

React v16 弃用

3.4 componentDidUpdate(prevProps, prevState)

组件更新完成之后执行, 首次渲染不会执行此方法

常用操作:

  1. 当组件更新后,可以在此处对 DOM 进行操作;
  2. 如果你对更新前后的 props 进行了比较,也可以选择在此处进行网络请求;(例如,当 props 未发生变化时,则不会执行网络请求)

注意: 在 React v16.4 componentDidUpdate 新增了第三个参数

componentDidUpdate(prevProps, prevState, snapshot)

如果组件实现了 getSnapshotBeforeUpdate() 生命周期(不常用),则它的返回值将作为 componentDidUpdate() 的第三个参数 “snapshot” 参数传递。否则此参数将为 undefined

3.5 componentWillReceiveProps(nextProps)

当一个组件要从父组件接收参数 只要父组件的 render 函数被重新执行了,子组件的这个声明周期就会被执行

此方法只调用于props引起的组件更新过程中,参数nextProps是父组件传给当前组件的新props。但父组件render 方法的调用不能保证重传给当前组件的props是有变化的,所以在此方法中根据nextProps和this.props来查明重传 的props是否改变,以及如果改变了要执行啥,比如根据新的props调用this.setState出发当前组件的重新render

React v16 弃用

4. 组件的卸载(Unmounting)阶段

  • componentWillUnmount

4.1 componentWillUnmount

会在组件卸载及销毁之前直接调用

常用于清理操作: 清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等

四、React v16.4 的生命周期

react 16.4 的生命周期 React v16.4 的生命周期图

1. 变更原因

React 以前是同步更新,如果更新的组件过多,就会阻塞线程,用户有操作无法立即响应,界面卡顿,影响用户体验。然后React v16推出的 React Fiber, 原来(React v16.0前)的生命周期再 React Fiber后就不合适了, 在 render 函数执行之前的所有函数都有可能执行多次。

(React v16.0前)在render前执行的生命周期

  • componentWillMount
  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate

禁止不能用比劝导开发者不要这样用的效果更好,所以除了 shouldComponentUpdate,其他在render函数之前的所有函数(componentWillMount,componentWillReceiveProps,componentWillUpdate)都被 getDerivedStateFromProps替代。

也就是用一个静态函数 getDerivedStateFromProps 来取代被过去的几个生命周期函数,就是强制开发者在render之前只做无副作用的操作,而且能做的操作局限在根据 props 和 state 决定新的 state

React v16.0刚推出的时候,是增加了一个componentDidCatch生命周期函数,这只是一个增量式修改,完全不影响原有生命周期函数;但是,到了React v16.3,大改动来了,引入了两个新的生命周期函数。

2. 新引入了两个新的生命周期函数: getDerivedStateFromProps , getSnapshotBeforeUpdate

2.1 getDerivedStateFromProps

getDerivedStateFromProps 本来(React v16.3中)是只在创建和父组件触发更新,getDerivedStateFromProps 才会被调用,不能被自身 setState 引发或者 forceUpdate 触发。

React v16.3

这样的话理解起来有点乱,在React v16.4中改正了这一点,让getDerivedStateFromProps 无论是 Mounting 还是 Updating,也无论是因为什么引起的 Updating,全部都会被调用,具体可看React v16.4 的生命周期图。

React v16.4后的

static getDerivedStateFromProps(props, state)

在组件创建时和更新时的render方法之前调用,它应该返回一个对象来更新状态,或者返回 null 来不更新任何内容。

getDerivedStateFromProps 的存在只有一个目的:让组件在 props 变化时更新 state

class Test extends React.Component {
     super(props);
    this.state = {
      num: 20,
      sum: 100,
    };
  }
  static getDerivedStateFromProps(props, state) {
    // return null;
    return {
      num: 60,
    };
  }

  render() {
    return (
      <>
       <div>{this.state.num}</div> {/* 60 */} 
        <div>{this.state.sum}</div> {/* 100 */}
      </>
    );
  }
}

2.2 getSnapshotBeforeUpdate

getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()。