彻底搞懂 React 生命周期

1. React的生命周期有哪些?

React 通常将组件生命周期分为三个阶段:

  • 装载阶段(Mount),组件第一次在DOM树中被渲染的过程;
  • 更新过程(Update),组件状态发生变化,重新更新渲染的过程;
  • 卸载过程(Unmount),组件从DOM树中被移除的过程;

在这里插入图片描述
https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

1.1 组件挂载阶段

挂载阶段组件被创建,然后组件实例插入到 DOM 中,完成组件的第一次渲染,该过程只会发生一次,在此阶段会依次调用以下这些方法:

  • constructor
  • getDerivedStateFromProps
  • render
  • componentDidMount
1.1.1 constructor

组件的构造函数,第一个被执行,若没有显式定义它,会有一个默认的构造函数,但是若显式定义了构造函数,我们必须在构造函数中执行 super(props)否则无法在构造函数中拿到this

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

constructor中通常只做两件事:

  • 初始化组件的 state
  • 给事件处理方法绑定 this
constructor(props) {
  super(props);
  // 不要在构造函数中调用 setState,可以直接给 state 设置初始值
  this.state = { counter: 0 }
  this.handleClick = this.handleClick.bind(this)
}
1.1.2 getDerivedStateFromProps
static getDerivedStateFromProps(props, state)

这是个静态方法,所以不能在这个函数里使用 this,有两个参数 propsstate,分别指接收到的新参数当前组件的 state 对象,这个函数会返回一个对象用来更新当前的 state 对象,如果不需要更新可以返回 null

该函数会在装载时,接收到新的 props 或者调用了 setStateforceUpdate 时被调用。如当接收到新的属性想修改 state ,就可以使用。

// 当 props.counter 变化时,赋值给 state 
class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      counter: 0
    }
  }
  static getDerivedStateFromProps(props, state) {
    if (props.counter !== state.counter) {
      return {
        counter: props.counter
      }
    }
    return null
  }
  
  handleClick = () => {
    this.setState({
      counter: this.state.counter + 1
    })
  }
  render() {
    return (
      <div>
        <h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
      </div>
    )
  }
}

现在可以显式传入 counter ,但是这里有个问题,如果想要通过点击实现 state.counter 的增加,但这时会发现值不会发生任何变化,一直保持 props 传进来的值。这是由于在 React 16.4^ 的版本中 setStateforceUpdate 也会触发这个生命周期,所以当组件内部 state 变化后,就会重新走这个方法,同时会把 state 值赋值为 props 的值。因此需要多加一个字段来记录之前的 props 值,这样就会解决上述问题。具体如下:

// 这里只列出需要变化的地方
class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      // 增加一个 preCounter 来记录之前的 props 传来的值
      preCounter: 0,
      counter: 0
    }
  }
  static getDerivedStateFromProps(props, state) {
    // 跟 state.preCounter 进行比较
    if (props.counter !== state.preCounter) {
      return {
        counter: props.counter,
        preCounter: props.counter
      }
    }
    return null
  }
  handleClick = () => {
    this.setState({
      counter: this.state.counter + 1
    })
  }
  render() {
    return (
      <div>
        <h1 onClick={this.handleClick}>Hello, world!{this.state.counter}</h1>
      </div>
    )
  }
}
1.1.3 render

renderReact 中最核心的方法,一个组件中必须要有这个方法,它会根据状态 state 和属性 props 渲染组件。这个函数只做一件事,就是返回需要渲染的内容,所以不要在这个函数内做其他业务逻辑,通常调用该方法会返回以下类型中一个:

  • React 元素:这里包括原生的 DOM 以及 React 组件;
  • 数组和 Fragment(片段):可以返回多个元素;
  • Portals(插槽):可以将子元素渲染到不同的 DOM 子树中;
  • 字符串和数字:被渲染成 DOM 中的 text 节点;
  • 布尔值或 null:不渲染任何内容。
1.1.4 componentDidMount()

componentDidMount()在组件挂载后(插入 DOM 树中)立即调用。该阶段通常进行以下操作:

  • 执行依赖于DOM的操作;
  • 发送网络请求;(官方建议)
  • 添加订阅消息(会在componentWillUnmount取消订阅);

如果在 componentDidMount 中调用 setState ,就会触发一次额外的渲染,多调用了一次 render 函数,由于它是在浏览器刷新屏幕前执行的,所以用户对此是没有感知的,但是我应当避免这样使用,这样会带来一定的性能问题,尽量是在 constructor 中初始化 state 对象

在组件装载之后,将计数数字变为1:

class App extends React.Component  {
  constructor(props) {
    super(props)
    this.state = {
      counter: 0
    }
  }
  componentDidMount () {
    this.setState({
      counter: 1
    })
  }
  render ()  {
    return (
      <div className="counter">
        counter值: { this.state.counter }
      </div>
    )
  }
}

1.2 组件更新阶段

当组件的 props 改变了,或组件内部调用了 setState/forceUpdate,会触发更新重新渲染,这个过程可能会发生多次。这个阶段会依次调用下面这些方法:

  • getDerivedStateFromProps
  • shouldComponentUpdate
  • render
  • getSnapshotBeforeUpdate
  • componentDidUpdate
1.2.1 shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState)

在说这个生命周期函数之前,来看两个问题:

  • setState 函数在任何情况下都会导致组件重新渲染吗? 例如下面这种情况:
this.setState({number: this.state.number})
  • 如果没有调用 setState,props 值也没有变化,是不是组件就不会重新渲染?

第一个问题答案是 ,第二个问题如果是父组件重新渲染时,不管传入的 props 有没有变化,都会引起子组件的重新渲染

  • 那么有没有什么方法解决在这两个场景下不让组件重新渲染进而提升性能呢?

这个时候 shouldComponentUpdate 登场了,这个生命周期函数是用来提升速度的,它是在重新渲染组件开始前触发的,默认返回 true,可以比较 this.propsnextPropsthis.statenextState 值是否变化,来确认返回 true 或者 false当返回 false 时,组件的更新过程停止,后续的 rendercomponentDidUpdate 也不会被调用。

注意:添加 shouldComponentUpdate 方法时,不建议使用深度相等检查(如使用 JSON.stringify()),因为深比较效率很低,可能会比重新渲染组件效率还低。而且该方法维护比较困难,建议使用该方法会产生明显的性能提升时使用。

1.2.2 getSnapshotBeforeUpdate
getSnapshotBeforeUpdate(prevProps, prevState)

这个方法在 render 之后,componentDidUpdate 之前调用,有两个参数 prevPropsprevState,表示更新之前的 propsstate,这个函数必须要和 componentDidUpdate 一起使用,并且要有一个返回值,默认是 null这个返回值作为第三个参数传给 componentDidUpdate

1.2.3 componentDidUpdate

componentDidUpdate() 在更新后会被立即调用,首次渲染不会执行此方法。 该阶段通常进行以下操作:

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

该方法有三个参数:

prevProps: 更新前的props
prevState: 更新前的state
snapshot: getSnapshotBeforeUpdate()生命周期的返回值

1.3 组件卸载阶段

卸载阶段只有一个生命周期函数,componentWillUnmount() 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作:

  • 清除 timer取消网络请求或清除
  • 取消在 componentDidMount() 中创建的订阅等;

这个生命周期在一个组件被卸载和销毁之前被调用,因此你不应该再这个方法中使用 setState,因为组件一旦被卸载,就不会再装载,也就不会重新渲染。

1.4 错误处理阶段

componentDidCatch(error, info),此生命周期在后代组件抛出错误后被调用。 它接收两个参数∶

  • error:抛出的错误。
  • info:带有 componentStack key 的对象,其中包含有关组件引发错误的栈信息

2. React常见的生命周期

在这里插入图片描述

React常见生命周期的过程大致如下:

(挂载阶段)

  • 挂载阶段,首先执行constructor构造方法,来创建组件
  • 创建完成之后,就会执行render方法,该方法会返回需要渲染的内容
  • 随后,React会将需要渲染的内容挂载到DOM树
  • 挂载完成之后就会执行componentDidMount生命周期函数

(更新阶段)

  • 如果我们给组件创建一个props(用于组件通信)、调用setState(更改state中的数据)、调用forceUpdate(强制更新组件)时,都会重新调用render函数
  • render函数重新执行之后,就会重新进行DOM树的挂载
  • 挂载完成之后就会执行componentDidUpdate生命周期函数

(卸载阶段)

  • 当移除组件时,就会执行componentWillUnmount生命周期函数
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值