React生命周期钩子函数

3,537 阅读9分钟

 前言

所谓的生命周期就是指某个事物从开始到结束的各个阶段,就好像是把人的出生到死亡分成一个个阶段,你肯定是在出生阶段起名字,而不会在成年或者死亡的阶段去起名字。当然在 React.js 中指的是组件从创建到销毁的过程,React 实例从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、卸载等一系列过程,我们称这是 React 的生命周期,各个阶段有相对应的事件钩子,用户可以在特定的阶段调用特定的方法。每个阶段组件内部的属性都是不一样的,所以一般特定的钩子做特定的事。

一、React生命周期图

【1】过去

​​

【2】现在

​​

二、React生命周期演变

这么多生命周期钩子,实际上总结起来只有三个过程:挂载、更新、卸载,挂载和卸载只会执行一次,更新会执行多次。

一个完整的React组件生命周期会依次调用如下钩子

【1】生命周期演变

之前(React 16.3 之前)

  • 挂载

    • constructor
    • componentWillMount
    • render
    • componentDidMount
  • 更新

    • componentWillReceiveProps
    • shouldComponentUpdate
    • componentWillUpdate
    • render
    • componentDidUpdate
  • 卸载

    • componentWillUnmount

现在

  • 挂载

    • constructor
    • getDerivedStateFromProps
    • render
    • componentDidMount
  • 更新

    • getDerivedStateFromProps
    • shouldComponentUpdate
    • render
    • getSnapshotBeforeUpdate
    • componentDidUpdate
  • 卸载

    • componentWillUnmount

【2】废弃和新增

原来(React v16.0前)的生命周期在React v16推出的Fiber之后就不合适了,因为如果要开启async rendering,在render函数之前的所有函数,都有可能被执行多次。所以除了shouldComponentUpdate,其他在render函数之前的所有函数(componentWillMount,componentWillReceiveProps,componentWillUpdate)都被getDerivedStateFromProps替代。

以下生命周期钩子将被逐渐废弃,看出特点了么,都是带有will的钩子

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

引入了以下两个生命周期钩子

  • getDerivedStateFromProps
  • getSnapshotBeforeUpdate

【3】注意

  1. getDerivedStateFromProps前面要加上static保留字,声明为静态方法,不然会被react忽略掉
  2. getDerivedStateFromProps里面的this为undefined

三、React生命周期方法介绍

【1】constructor(props):初始化钩子函数

constructor()中完成了React数据的初始化,它接受两个参数:props和context,当想在函数内部使用这两个参数时,需使用super()传入这两个参数。如果没有初始化状态(state),并且没有绑定方法,通常不需要为 React 组件实现一个构造函数。

注意:

  1. 只要使用了constructor()就必须写super(),否则会导致this指向错误。
  2. 不需要在构造函数中调用 setState(),只需将初始状态设置给 this.state 即可 。

React 构造函数通常只用于两个目的:

  1. 通过分配一个对象到this.state来初始化本地state
  2. 将事件处理程序方法绑定到实例
constructor(props) {
 super(props);
 // 不要这样做
 this.state = { color: props.color };
}

【2】componentWillMount():组件将要挂载时触发的函数

这是组件挂载到DOM之前的生命周期钩子。componentWillMount()一般用的比较少,它更多的是在服务端渲染时使用。它代表的过程是组件已经经历了constructor()初始化数据后,但是还未渲染DOM时。

【3】render():组件挂载时触发的函数

render函数是类组件中唯一必须的方法,其余生命周期不是必须要写。 组件渲染时会走到该生命周期,展示的组件都是由 render() 生命周期的返回值来决定。render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染。

【4】componentDidMount():组件挂载完成时触发的函数

这是组件挂载到DOM之后的生命周期钩子。组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染

【5】componentWillUnmount ():组件将要销毁时触发的函数

这是组件卸载之前的生命周期钩子,在此处完成组件的卸载和数据的销毁。

【6】componentWillReceiveProps (nextProps):父组件中改变了props传值时触发的函数

  1. 在接受父组件改变后的props需要重新渲染组件时用到的比较多

  2. 接受一个参数nextProps

  3. 通过对比nextProps和this.props,将nextProps的state为当前组件的state,从而重新渲染组件

    componentWillReceiveProps (nextProps) { nextProps.isOpen !== this.props.isOpen && this.setState({ isOpen:nextProps.isOpen }, () => { //将state更新为nextProps,在setState的第二个参数(回调)可以打印出新的state }) }

【7】shouldComponentUpdate(nextProps,nextState):是否要更新组件时触发的函数

  1. 这个生命周期钩子是一个开关,判断是否需要更新,主要用来优化性能(部分更新),如果开发者调用this.forceUpdate强制更新,React组件会无视这个钩子。
  2. 对于组件来说,只有状态发生改变,才需要重新渲染。所以shouldComponentUpdate生命周期钩子暴露了两个参数,开发者可以通过比较this.props和nextProps、this.state和nextState来判断状态到底有没有发生改变,再相应的返回true或false。
  3. 唯一用于控制组件重新渲染的生命周期,由于在react中,setState以后,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新
  4. 因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断

【8】componentWillUpdate (nextProps,nextState):将要更新组件时触发的函数

shouldComponentUpdate生命周期钩子返回true以后,组件进入重新渲染的流程,进入componentWillUpdate,这里同样可以拿到nextProps和nextState。

【9】componentDidUpdate(prevProps,prevState):组件更新完成时触发的函数

这是组件更新之后触发的生命周期钩子,组件更新完毕后,react只会在第一次初始化成功会进入componentDidmount,之后每次重新渲染后都会进入这个生命周期,这里可以拿到prevProps和prevState,即更新前的props和state。

【10】static getDerivedStateFromProps(nextProps,prevState):静态方法生命周期钩子

getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。为什么getDerivedStateFromProps生命周期钩子要设计成静态方法呢?因为这样开发者就访问不到this也就是实例了,也就不能在里面调用实例方法或者setsState了,用一个静态函数getDerivedStateFromProps来取代被废弃的其他几个生命周期函数,就是强制开发者在render之前只做无副作用的操作,而且能做的操作局限在根据props和state决定新的state 而已。

【11】 getSnapshotBeforeUpdate(prevProps,prevState):保存状态快照

它是用来代替componentWillUpdate生命周期钩子函数的,常见的 componentWillUpdate 的用例是在组件更新前,读取当前某个 DOM 元素的状态,并在 componentDidUpdate 中进行相应的处理。

这两者的区别在于:

  1. 在 React 开启异步渲染模式后,在 render 阶段读取到的 DOM 元素状态并不总是和 commit 阶段相同,这就导致在
    componentDidUpdate 中使用 componentWillUpdate 中读取到的 DOM 元素状态是不安全的,因为这时的值很有可能已经失效了。
  2. getSnapshotBeforeUpdate 会在最终的 render 之前被调用,也就是说在 getSnapshotBeforeUpdate 中读取到的 DOM 元素状态是可以保证与 componentDidUpdate 中一致的。此生命周期返回的任何值都将作为参数传递给componentDidUpdate()。

四、实例

【1】挂载阶段

创建一个子组件,代码如下,在父组件引入渲染即可

import React ,{Component} from 'react'

class Child extends Component{
    // 初始化
	constructor(props){
		console.log('01构造函数')		
		super(props)
		this.state={}
	}
	// 组件将要挂载时候触发的生命周期函数
	componentWillMount(){
		console.log('02组件将要挂载')
	}
	// 组件挂载完成时候触发的生命周期函数
	componentDidMount(){
		console.log('04组件挂载完成')
    }
    // 组件挂载时触发的生命周期函数
	render(){
		console.log('03数据渲染render')
		return(
			<div>Child组件</div>
		) 
	}
}
export default Child

控制台打印结果顺序如下:

01构造函数
02组件将要挂载
03数据渲染render
04组件挂载完成

【2】更新阶段

数据更新的话第一步是shouldComponentUpdate确认是否要更新数据,当这个函数返回的是true的时候才会进行更新,并且这个函数可以声明两个参数nextProps和nextState,nextProps是父组件传给子组件的值,nextState是数据更新之后值,这两个值可以在这个函数中获取到。第二步当确认更新数据之后componentWillUpdate将要更新数据,第三步依旧是render,数据发生改变render重新进行了渲染。第四步是componentDidUpdate数据更新完成。

import React, { Component } from 'react'

class Child extends Component {
  constructor(props) {
    super(props)
    this.state = {
      msg: '我是一个msg数据'
    }
  }

  //是否要更新数据,如果返回true才会更新数据
  shouldComponentUpdate(nextProps, nextState) {
    console.log('01是否要更新数据')
    return true;				//返回true,确认更新
  }
  //将要更新数据的时候触发的生命周期函数
  componentWillUpdate() {
    console.log('02组件将要更新')
  }
  //更新完成时触发的生命周期函数
  componentDidUpdate() {
    console.log('04组件更新完成')
  }
  //更新数据方法
  setMsg() {
    this.setState({
      msg: '我是改变后的msg数据'
    })
  }
  render() {
    console.log('03数据渲染render')
    return (
      <div>
        {this.state.msg}
        <button onClick={() => this.setMsg()}>点我更新msg的数据</button>
      </div>
    )
  }
}
export default Child

点击按钮更新数据,控制台打印结果顺序如下:

01是否要更新数据
02组件将要挂载
03数据渲染render
04组件更新完成

五、参考

React官网: zh-hans.reactjs.org/

文章每周持续更新,可以微信搜索「 前端大集锦 」第一时间阅读,回复【视频】【书籍】领取200G视频资料和30本PDF书籍资料