关于React生命周期的学习

498 阅读7分钟

故事: 以下都是拾人牙慧,主要是方便自己的学习总结,

组件初始化 constructor

React借用class类的constructor充当初始化钩子。 

React几乎没做什么手脚,但是因为我们只允许通过特定的途径给组件传递参数,所以constructor的参数实际上是被React规定好的。 

React规定constructor有三个参数,分别是props、context和updater。 

props是属性,它是不可变的

context是全局上下文

updater是包含一些更新方法的对象

this.setState最终调用的是this.updater.enqueueSetState方法

this.forceUpdate最终调用的是this.updater.enqueueForceUpdate方法

所以这些API更多是React内部使用,暴露出来是以备开发者不时之需。 在React中,因为所有class组件都要继承自Component类或者PureComponent类,因此和原生class写法一样,要在constructor里首先调用super方法,才能获得this。 

constructor生命周期钩子的最佳实践是在这里初始化this.state,而且禁止在构造函数中调用setState,可以直接给state设置初始值

当然,你也可以使用属性初始化器来代替,如下:

import React, { Component } from 'react';

class App extends Component {
    state = {
        name: 'biu'
    };
}

export default App;

组件挂载 

1)componentWillMount()

注意(这是React不再推荐使用的API。)

其实componentWillMount和挂载是同步执行的,意味着执行完这个钩子,立即挂载。而向服务器请求数据是异步执行的。所以无论请求怎么快,都要排在同步任务之后再处理,这是辈分问题。 也就是说,永远不可能在这里将数据插入元素一同挂载。 并不是说不能在这里请求数据,而是达不到你臆想的效果。 

它被废弃的原因主要有三点:  

本来它就没什么用。估计当初是为了成双成对所以才创造了它吧。 

如果它声明了定时器或者订阅器,在服务端渲染中,componentWillUnmount生命周期钩子中的清除代码不会生效。因为如果组件没有挂载成功,componentWillUnmount是不会执行的。 

在异步渲染中,它的表现不稳定,因此在componentWillMount里做 AJAX 请求实在不是一个明智之举,因为对于同构项目中,componentWillMount是会被调用的。 

初始化this.state应该在constructor生命周期钩子中完成,请求数据应该在componentDidMount生命周期钩子中完成,所以它不仅被废弃了,连继任者都没有。

综上所述,componentWillMount其实本来没有什么主要作用,如果你的代码规范,去掉的话,不会对现在的项目产生什么影响。


static getDerivedStateFromProps(nextProps, prevState)

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

2)render()

作为一个组件,最核心的功能就是把元素挂载到DOM上,所以render生命周期钩子是一定会用到的。 render生命周期钩子怎么接收模板呢?当然是你return给它。 但是不推荐在return之前写过多的逻辑,如果逻辑过多,可以封装成一个函数。

render() {
    // 这里可以写一些逻辑
    return (
        <div>生命周期</div>
    )
}

注意,千万不要在render生命周期钩子里调用this.setState,因为this.setState会引发render,这下就没完没了了。

3)componentDidMount  [非常重要的钩子]

这是组件挂载到DOM之后的生命周期钩子。 这可能是除了render之外最重要的生命周期钩子,因为这时候组件的各方面都准备就绪,天地任你闯。

组件更新 

1)componentWillReceiveProps(nextProps)

注意:是React不再推荐使用的API。 

componentWillReceiveProps生命周期钩子只有一个参数,更新后的props。 

该声明周期函数可能在两种情况下被触发:  

组件接收到了新的属性。 

组件没有收到新的属性,但是由于父组件重新渲染导致当前组件也被重新渲染。

初始化时并不会触发该生命周期钩子。 同样,因为Fiber机制的引入,这个生命周期钩子有可能会多次触发。

getDerivedStateFromProps

这个方法在装载阶段已经讲过了,这里不再赘述,记住在更新阶段,无论我们接收到新的属性,调用了setState还是调用了forceUpdate,这个方法都会被调用

2)shouldComponentUpdate(nextProps, nextState)

这个生命周期钩子是一个开关,判断是否需要更新,主要用来优化性能。 有一个例外,如果开发者调用this.forceUpdate强制更新,React组件会无视这个钩子。

shouldComponentUpdate生命周期钩子默认返回true。也就是说,默认情况下,只要组件触发了更新,组件就一定会更新。React把判断的控制权给了开发者。  

不过周到的React还提供了一个PureComponent基类,它与Component基类的区别是PureComponent自动实现了一个shouldComponentUpdate生命周期钩子。 

对于组件来说,只有状态发生改变,才需要重新渲染。所以shouldComponentUpdate生命周期钩子暴露了两个参数,开发者可以通过比较this.props和nextProps、this.state和nextState来判断状态到底有没有发生改变,再相应的返回true或false

export default class Life extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            count:1
        }

    }
    shouldComponentUpdate(nextProps, nextState) {
        //接受两个参数
        //返回true则表示通过比较新返回的元素和之前渲染的元素,两者不相等,此时更新DOM
        return true
    }
    handleClick = () => {
        this.setState({
            count: this.state.count + 1
        })
    }
    render() {
        // 这里可以写一些逻辑
        console.log(this.state.count)
        return (
            <div>
                <div>生命周期</div>
                <button onClick={this.handleClick}>{this.state.count}</button>
            </div>
        )
    }
}

3)componentWillUpdate(nextProps, nextState)

注意:这是React不再推荐使用的API。 

shouldComponentUpdate生命周期钩子返回true,或者调用this.forceUpdate之后,会立即执行该生命周期钩子。 要特别注意,componentWillUpdate生命周期钩子每次更新前都会执行,所以在这里调用this.setState非常危险,有可能会没完没了。 同样,因为Fiber机制的引入,这个生命周期钩子有可能会多次调用。

getSnapshotBeforeUpdate(prevProps, prevState)

在 React 更新 DOM 之前调用,此时state已更新; 返回值作为componentDidUpdate的第3个参数; 一般用于获取render之前的 DOM 数据


4)componentDidUpdate(nextProps, nextState, snapshot)

这是组件更新之后触发的生命周期钩子。 搭配getSnapshotBeforeUpdate生命周期钩子使用的时候,第三个参数是getSnapshotBeforeUpdate的返回值。 同样的,componentDidUpdate生命周期钩子每次更新后都会执行,所以在这里调用this.setState也非常危险,有可能会没完没了。

更新 生命周期函数的执行顺序 

  • static getDerivedStateFromProps 
  • shouldComponentUpdate render 
  • getSnapshotBeforeUpdate 
  • componentDidUpdate

组件卸载

componentWillUnmount()

这是组件卸载之前的生命周期钩子。 为什么组件快要卸载了还需要沉思时刻呢? 因为开发者要擦屁股吖。 React的最佳实践是,组件中用到的事件监听器、订阅器、定时器都要在这里销毁。 当然我说的事件监听器指的是这种:

componentDidMount() {
    document.addEventListener('click', () => {});
}

因为下面这种React会自动销毁,不劳烦开发者了。

render() {
    // 这里可以写一些逻辑
    return (
        <div>
            <div>生命周期</div>
            <button onClick={this.handleClick}>{this.state.count}</button>
        </div>
    )
}

整理V16 生命周期函数用法建议

class ExampleComponent extends React.Component {
  // 用于初始化 state
  constructor() {}
  // 用于替换 `componentWillReceiveProps` ,该函数会在初始化和 `update` 时被调用
  // 因为该函数是静态函数,所以取不到 `this`
  // 如果需要对比 `prevProps` 需要单独在 `state` 中维护
  static getDerivedStateFromProps(nextProps, prevState) {}
  // 判断是否需要更新组件,多用于组件性能优化
  shouldComponentUpdate(nextProps, nextState) {}
  // 组件挂载后调用
  // 可以在该函数中进行请求或者订阅
  componentDidMount() {}
  // 用于获得最新的 DOM 数据
  getSnapshotBeforeUpdate() {}
  // 组件即将销毁
  // 可以在此处移除订阅,定时器等等
  componentWillUnmount() {}
  // 组件销毁后调用
  componentDidUnMount() {}
  // 组件更新后调用
  componentDidUpdate() {}
  // 渲染组件函数
  render() {}
  // 以下函数不建议使用
  UNSAFE_componentWillMount() {}
  UNSAFE_componentWillUpdate(nextProps, nextState) {}
  UNSAFE_componentWillReceiveProps(nextProps) {}
}