前言

对于react的生命周期一直是在工作中很重要的,我们往往需要在需要的时间段做自己需要的事情,在 React 中,生命周期大致上可以分为初始化(Initialization)、挂载(Mounting)、更新(Updating)和卸载(Unmounting) 这几个阶段,每个阶段又会分别调用不同的生命周期函数。当然随着react版本的更新,也会出现相对的变化,所以我们现在来参考总结下

大部分团队不见得会跟进升到16版本,所以16前的生命周期还是很有必要掌握的,何况16也是基于之前的修改

一 初始化阶段 Initialization

也就是以下代码中类的构造方法( constructor() ),Test类继承了react Component这个基类,也就继承这个react的基类,才能有render(),生命周期等方法可以使用,这也说明为什么 函数组件不能使用这些方法的原因。super(props) 用来调用基类的构造方法( constructor() ), 也将父组件的props注入给子组件,功子组件读取(组件中props只读不可变,state可变)。 而 constructor() 用来做一些组件的初始化工作,如定义this.state的初始内容。

注意:
constructor不算生命周期函数。constructor我们叫构造函数,它是ES6的基本语法。虽然它和生命周期函数的性质一样,但不能认为是生命周期函数。但是你要心里把它当成一个生命周期函数,我个人把它看成React的Initialization阶段,定义属性(props)和状态(state)。

import React, {
    Component } from 'react'
class Test extends Component {
   
  constructor(props) {
   
  super(props)
  }
}

二 挂载阶段 Mounting

  1. componentWillMount: 在组件即将被挂载到页面的时刻执行。

在组件挂载到DOM前调用,且只会被调用一次,在这边调用this.setState不会引起组件重新渲染,也可以把写在这边的内容提前到constructor()中,所以项目中很少用。

  1. render: 页面state或props发生变化时执行。

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

  1. componentDidMount: 组件挂载完成时被执行。

组件挂载到DOM后调用,且只会被调用一次

export default class App extends Component {
   
  componentWillMount () {
   
    console.log('componentWillMount----组件将要挂载到页面的时刻')
  }
  render() {
   
    console.log('render---组件挂载中.......')
    return (
      <div>
        <TodoList/> 
        <ReduxTodoList/>
      </div>
    )
  }
  componentDidMount () {
   
    console.log('componentDidMount----组件挂载完成的时刻执行')
  }
}

打印的结果:

componentWillMount----组件将要挂载到页面的时刻执行
render----开始挂载渲染
componentDidMount----组件挂载完成的时刻执行

这就是挂载阶段的生命周期的执行顺序,当然他跟书写顺序没有关系

注意:
componentWillMountcomponentDidMount这两个生命周期函数,只在页面刷新时执行一次,而render函数是只要有state和props变化就会执行,这个初学者一定要注意。

三 更新阶段 Updation

这个阶段是较为复杂的阶段,由上面的图可以看出造成组件更新有两类的情况,需要先明确下React组件更新机制。setState引起的state更新或父组件重新render引起的props更新,更新后的state和props相对之前无论是否有变化,都将引起子组件的重新render

  1. shouldComponentUpdate在组件更新之前,自动被执行,它要求返回一个布尔类型的结果,必须有返回值,这里就直接返回一个true了(真实开发中,这个是有大作用的,能有有效的解决性能的问题),如果你返回了false,这组件就不会进行更新了。 简单点说,就是返回true,就同意组件更新;返回false,就反对组件更新。此方法通过比较nextProps,nextState及当前组件的this.props,this.state,返回true时当前组件将继续执行更新过程,返回false则当前组件更新停止,以此可用来减少组件的不必要渲染,优化组件性能。

  2. componentWillUpdate在组件更新之前,但shouldComponenUpdate之后被执行,但是shouldComponenUpdate返回的是false的时候就会反对组件的更新,这个函数就不会执行

  3. componentDidUpdate在组件更新之后执行,它是组件更新的最后一个环节

export default class App extends Component {
   
  shouldComponentUpdate () {
   
    console.log ('1.shouldComponentUpdate----组件更新之前')
    return true
  }
  componentWillUpdate () {
   
    console.log ('2.componentWillUpdate---组件更新前,shouldComponentUpdate函数之后执行 ')
  }
  render() {
   
    console.log('3.render---组件挂载渲染.......')
    return (
      <div>
        <TodoList/> 
        <ReduxTodoList/>
      </div>
    )
  }
  componentDidUpdate () {
   
    console.log('componentDidUpdate----组件更新完成的时刻执行')
  }
}

打印结果与执行顺序

1-shouldComponentUpdate---组件发生改变前执行
2-componentWillUpdate---组件更新前,shouldComponentUpdate函数之后执行
3-render----开始挂载渲染
4-componentDidUpdate----组件更新之后执行
  1. 那么上图中的componentWillReceiveProps在什么时候执行呢,其实子组件接收到父组件传递过来的参数,父组件render函数重新被执行,这个生命周期就会被执行。
    注意:
    使用componentWillReceiveProps组件第一次存在于Dom中,函数是不会被执行的;
    如果已经存在于Dom中,函数才会被执行。
    当父组件传递的 props 即将引起组件更新时会被调用,该方法接受一个参数指的是当前父组件传递给组件的最新的 props 状态数据。在这个生命周期方法中,我们可以根据比较 nextProps 和 this.props 新旧 props 的值查明 props 是否改变,依次做一些数据处理的逻辑。

销毁阶段 Unmounting

这个阶段就只一个生命周期函数:componentWillUnmount,此方法在组件被卸载前调用,可以在这里执行一些清理工作,比如清楚组件中使用的定时器,清楚componentDidMount中手动创建的DOM元素、解绑事件等,以避免引起内存泄漏

总结利用生命周期函数的优化页面:

  1. componentDidMount生命周期函数里请求ajax,建议在componentDidMount函数里执行,因为在render里执行,会出现很多问题,比如一直循环渲染;在componentWillMount里执行,在使用RN时,又会有冲突。所以强烈建议在componentDidMount函数里作ajax请求。
  2. 在父组件更新的时候传递到子组件的时候导致pros反正改变,子组件的render函数不停触发,出现性能问题,我们可以这个一般发生在更新阶段,所以利用shouldComponentUpdate
shouldComponentUpdate(nextProps,nextState){
   
    if(nextProps.content !== this.props.content){
   
        return true
    }else{
   
        return false
    }

}

React v16.4 的生命周期

变更缘由

原来(React v16.0前)的生命周期在React v16推出的Fiber之后就不合适了,因为如果要开启async rendering,在render函数之前的所有函数,都有可能被执行多次。
原来(React v16.0前)的生命周期有哪些是在render前执行的呢?

  • componentWillMount
  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate

如果开发者开了async rendering,而且又在以上这些render前执行的生命周期方法做A JAX请求的话,那AJAX将被无谓地多次调用。。。明显不是我们期望的结果。而且在componentWillMount里发起AJAX,不管多快得到结果也赶不上首次render,而且componentWillMount在服务器端渲染也会被调用到(当然,也许这是预期的结果),这样的IO操作放在componentDidMount里更合适。

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

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

新引入了两个新的生命周期函数:

1. getDerivedStateFromProps

getDerivedStateFromProps 本来(React v16.3中)是只在创建和更新(由父组件引发部分),也就是不是不由父组件引发,那么getDerivedStateFromProps也不会被调用,如自身setState引发或者forceUpdate引发
这样的话理解起来有点乱,在React v16.4中改正了这一点,让getDerivedStateFromProps无论是Mounting还是Updating,也无论是因为什么引起的Updating,全部都会被调用,具体可看React v16.4 的生命周期图。
getDerivedStateFromProps(props, state) 在组件创建时和更新时的render方法之前调用,它应该返回
一个对象来更新状态,或者返回null来不更新任何内容

每当父组件引发当前组件的渲染时,getDerivedStateFromProps 会被调用,这样我们有机会可以根据新的 props 和之前的 state 来调整新的 state。如果放在三个被 deprecate 生命周期函数中实现比较纯,没有副作用的话,就可以搬到 getDerivedStateFromProps 了;如果不幸做了类似 AJAX 之类的操作,首先要反省为什么自己当初这么做,然后搬到 componentDidMount 或者 componentDidUpdate 中去。目前当你使用三个被 deprecate 生命周期函数时,开发模式下会有红色警告,要求你使用 UNSAFE_ 前缀。可能会在打一次大版本更新时直接废弃,所以那些抱有侥幸心理的开发者还是放弃使用吧。

2. getSnapshotBeforeUpdate

getSnapshotBeforeUpdate() 被调用于render之后,可以读取但无法使用DOM的时候。它使您的组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置)。此生命周期返回的任何值都将作为参数传递给componentDidUpdate()。
官网给的例子:

class ScrollingList extends React.Component {
   
  constructor(props) {
   
    super(props);
    this.listRef = React.createRef();
  }
  getSnapshotBeforeUpdate(prevProps, prevState) {
   
    //我们是否要添加新的 items 到列表?
    // 捕捉滚动位置,以便我们可以稍后调整滚动.
    if (prevProps.list.length < this.props.list.length) {
   
      const list = this.listRef.current;
      return list.scrollHeight - list.scrollTop;
    }
    return null;
  }
  componentDidUpdate(prevProps, prevState, snapshot) {
   
    //如果我们有snapshot值, 我们已经添加了 新的items.
    // 调整滚动以至于这些新的items 不会将旧items推出视图。
    // (这边的snapshot是 getSnapshotBeforeUpdate方法的返回值)
    if (snapshot !== null) {
   
      const list = this.listRef.current;
      list.scrollTop = list.scrollHeight - snapshot;
    }
  }
  render() {
   
    return (
      <div ref={
   this.listRef}>{
   /* ...contents... */}</div>
    );
  }
}

注意:
当你同时使用了 getDerivedStateFromProps、 getSnapshotBeforeUpdate 新的生命周期 API 和 deprecate 生命周期函数时,deprecate 生命周期函数会被直接忽略掉,并不会适时执行!