React Class组件生命周期详解

2,225 阅读7分钟

原文参考链接:

juejin.cn/post/684490…

juejin.cn/post/684490…

1. 旧版生命周期(v16.0之前)

在这里插入图片描述 1. 组件挂载过程:

(1) constructor()

constructor 构造方法常用来初始化state

constructor是ES6对类的默认方法,通过 new 命令生成对象实例时自动调用该方法。并且,该方法是类中必须有的,如果没有显示定义,则会默认添加空的constructor( )方法。

当存在constructor的时候必须手动调用super方法。如果在constructor中想使用this关键字,就必须先调用super方法。在constructor中如果要访问this.props需要在super中传入props。

但是无论有没有定义constructor,super是否传入props参数,在react的其他生命周期中this.props都是可以使用的,这是React自动附带的。

class MyClass extends React.Component {
    constructor(props){
        super(props);
        this.state = {
            list: this.props.List
        };
        this.state.list = []; //修改state
        setTimeout(() => {
          this.setState({list: [1, 2, 3]}); //异步操作后 setState 触发渲染
        }, 100);
    }
}


需要注意的是,在构造函数里定义了state,当你想在一些操作后修改state,只需要直接操作this.state即可, 如果你在构造函数里执行了异步操作,就需要调用setState来触发重新渲染。在其余的地方当你需要改变state的时候只能使用this.setState,这样 React 才会触发刷新UI渲染。

(2) componentWillMount()

组件即将渲染之前

在组件挂载之前调用且全局只调用一次。如果在这个钩子里可以setState,render后可以看到更新后的state,不会触发重复渲染。

(3) render()

渲染组件(虚拟dom)

render是一个React组件必须定义的生命周期,用来渲染dom。render必须返回reactDom(即jsx)

不要在render里面setState,会触发死循环导致栈溢出。

(3) componentDidMount()

组件挂载完成后

在组件挂载完成后调用,且全局只调用一次。只有到了这步后,才可以在这里使用refs,获取真实dom元素。该钩子内也可以发起异步请求,并在异步请求中可以进行setState。

当子组件componentDidMount触发后,父组件才会触发componentDidMount

如果有初始http异步请求,应该放在componentWiilMount还是componentDidMount中?

结论:应该放在componentDidMount中

1:在16.3之前的react版本中 ,react是同步渲染的,在componentWillMount中接口调用,有可能不会触发界面渲染,一般来说异步请求后我们都会存在setState的操作(比如获取页面的一些初始数据),在componentWiilMount中setState是不会触发重新渲染(即页面不会显示最新获取的数据,异步请求返回结果的时间一定在首次render之后)。而在componentDidMount中渲染一定会触发界面渲染。

2:在16.3之后react开始异步渲染(React 下一代调和算法 Fiber 会通圌过开始或停止渲染的方式优化应用性能,其会影响到 componentWillMount 的触发次数)。对在异步渲染模式下,使用componentWillMount会被多次调用,并且存在内存泄漏等问题。componentWillMount终将会被废弃,目前被标记为不安全

3:有时候我们需要在拿到异步请求的数据后需要对dom元素进行操作(比如ref操作),而放在componentWillMount中,在同步模式渲染下我们还拿不到渲染后的dom。

4:如果使用服务端渲染的话,componentWillMount会在服务端和客户端各自执行一次,这会导致请求两次,而componentDidMount只会在客户端进行。

所以,无论你的react版本是16.3之前还是之后,都应该将初始异步请求放在componentDidMount中

componentDidMount() {
	axios.get('/auth/getTemplate').then(res => {
		const {TemplateList = []} = res;
		this.setState({TemplateList});
	});
}

2. 组件更新过程:

(1) componentWillReceiveProps (nextProps)

即将接收新的props

props发生变化以及父组件重新渲染时(父组件更新渲染时,无论当前组件的props是否发生变化)都会触发该生命周期,在该钩子内可以通过参数nextProps获取变化后的props参数,通过this.props访问之前的props。该生命周期内可以进行setState

(2) **shouldComponentUpdate(nextProps, nextState) **

是否更新渲染

组件挂载之后,每次调用setState后都会调用shouldComponentUpdate判断是否需要重新渲染组件。默认返回true,更新渲染。返回false则不触发渲染。在比较复杂的应用里,有一些数据的改变并不影响界面展示,可以在这里做判断,优化渲染效率。

(3)componentWillUpdate(nextProps, nextState)

组件即将更新渲染

shouldComponentUpdate返回true或者调用forceUpdate之后,componentWillUpdate会被调用。

当父组件componentWillUpdate触发后,子组件才会触发componentWillUpdate

不能在该生命周期中setState,会触发重复循环

(4)componentDidUpdate()

组件完成更新渲染

除了首次render之后调用componentDidMount,其它render结束之后都是调用componentDidUpdate。此时才可以拿到更新后的dom

当子组件componentDidUpdate触发后,父组件才会触发componentDidUpdate

该钩子内setState有可能会触发重复渲染,需要自行判断,否则会进入死循环。

componentDidUpdate() {
    if(condition) {
        this.setState({..}) // 设置state
    } else {
        // 不再设置state
    }
}

3. 组件卸载过程:

只有一个生命周期

(1) componentWillUnmount()

组件即将被卸载

当子组件componentWillUnmount触发后,父组件才会触发componentWillUnmount

组件被卸载的时候调用。一般在componentDidMount里面注册的事件(比如addEventListener)需要在这里清除掉。

2. 新版生命周期(v16.3之后)

2.1 废弃的生命周期

react v16.3废弃了以下三个生命周期,并在开头添加上了UNSAFE标志(只是不建议使用,将在v17.0中正式删除)

componentWillMount(UNSAFE_componentWillMount) componentWillReceiveProps(UNSAFE_componentWillReceiveProps) componentWillUpdate(UNSAFE_componentWillUpdate)

其废弃的原因主要是考虑到新的异步渲染模式以及这些生命周期存在的一些副作用(比如会被多次调用)

2.2 新增的生命周期

static getDerivedStateFromProps getSnapshotBeforeUpdate

(1) static getDerivedStateFromProps(nextProps,prevState)

  • 当组件实例化的时候,这个方法替代了componentWillMount。而当接收到新的props时,该方法替代了componentWillReceiveProps 和 componentWillUpdate
  • 触发时机:会在每次组件被重新渲染前被调用,这意味无论是父组件的更新,props的变化,组件内部执行了 setState。,它都会被调用.
  • 这个方法是个static的方法,因此使用this在这个方法中并不指代本实例组件,如果打印出来会发现这个this在这个方法中是null.
  • 而且这个方法会返回值.当需要更新State状态时,需要返回一 个object ,如果不需要任何更新,则返回null即可.
  • 参数:参数是组件接收到的新的props和组件当前的状态.用户需要在这个函数中返回一个对象,它将作为setStat中的Updater来更新组件。

在这里插入图片描述

注意:componentWillReceiveProps和getDerivedStateFromProps同时存在,控制台报错

(2)getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate 生命周期方法在 render 之后,在componenDidUpdate之前被调用。给了一个机会去获取 DOM 信息,计算得到并返回一个 snapshot,这个 snapshot 会作为 componentDidUpdate 的第三个参数传入。如果你不想要返回值,请返回 null,不写的话控制台会有警告。

并且,这个方法一定要和 componentDidUpdate 一起使用,否则控制台也会有警告。getSnapshotBeforeUpdate 与 componentDidUpdate 一起,这个新的生命周期涵盖过时的 UNSAFE_componentWillUpdate 的所有用例。

getSnapshotBeforeUpdate(prevProps, prevState) {
  console.log('#enter getSnapshotBeforeUpdate');
  return 'foo';
}

componentDidUpdate(prevProps, prevState, snapshot) {
  console.log('#enter componentDidUpdate snapshot = ', snapshot);
}

上面这段代码可以看出来这个 snapshot 怎么个用法,snapshot 乍一看还以为是组件级别的某个“快照”,其实可以是任何值,到底怎么用完全看开发者自己,getSnapshotBeforeUpdate 把 snapshot 返回,然后 DOM 改变,然后 snapshot 传递给 componentDidUpdate。

官方给了一个例子,用 getSnapshotBeforeUpdate 来处理 scroll,并且说明了通常不需要这个函数,只有在重新渲染过程中手动保留滚动位置等情况下非常有用,所以大部分开发者都用不上,也就不要乱用。

补充:函数组件是没有生命周期的,所以只要渲染到该函数组件时,默认每次都会重新渲染