首发于极乐科技
React 新旧生命周期的思考理解

React 新旧生命周期的思考理解

React 在v16.3版本中将 componentWillMount, componentWillReceiveProps 以及componentWillUpdate 加上了UNSAFE_前缀,这些钩子将在React 17.0废除,如果您确实选择继续使用它,您应该使用UNSAFE_componentWillMount()。同时React引入两个钩子函数来替代它们,分别是getDerivedStateFromPropsgetSnapshotBeforeUpdate。至于为什么去除值得我们去思考 以及总结,对于新钩子使用有哪些需要注意地方?

  1. componentWillMount
    此钩子在页面初始化render之前会执行一次或多次(async rendering)。
    使用陷阱1:在此生命周期进行请求数据来解决首页白屏问题,但是并没有解决掉,由于JavaScript中异步事件的性质,当您启动API调用时,浏览器会在此期间返回执行其他工作。当React渲染一个组件时,它不会等待componentWillMount它完成任何事情 - React继续前进并继续render,没有办法“暂停”渲染以等待数据到达。
    解决办法:你应该展示一些骨架屏来提示,不要去迭代数组,因为为underfind。请求数据应该在componentDidMount操作更加合适。对于同步的状态改变,是可以放在componentWillMount,对于异步的,最好好放在componentDidMount。但如果此时有若干细节需要处理,比如你的组件需要渲染子组件,而且子组件取决于父组件的某个属性,那么在子组件的componentDidMount中进行处理会有问题:因为此时父组件中对应的属性可能还没有完整获取,因此就让其在子组件的componentDidUpdate中处理。
    使用陷阱2: 利用componentWillMount以及componentWillUnmount对称性来进行订阅取消事件,但是在开启async rendering,在render函数之前的所有函数,都有可能被执行多次,并且组件的渲染也很有可能会被其他的事务所打断,导致componentWillUnmount不会被调用。而componentDidMount就不存在这个问题,在componentDidMount被调用后,componentWillUnmount一定会随后被调用到。
  2. componentDidMount
    这个钩子为啥我没有想说的???估计感触不深,后续填充。。。。如上所说,在这里进行数据交互比较合适,适合做一些具有副作用的事情。在render()之后执行,也可以进行一些dom操作。
  3. componentWillReceiveProps
    触发时机: 在render期间不会执行,当父组件导致子组件更新的时候, 即使接收的 props 并没有变化, 这个函数也会被调用
    使用场景: 这个方法很适合父子组件之间的联动,适合在父组件根据某个状态控制子组件的渲染或者销毁,通过对比this.props以及nextProps来对本组件内的state进行变更,或者执行某些方法来进行组件的重新渲染。
//这种方式十分适合父子组件的互动,通常是父组件需要通过某些状态控制子组件渲染亦或销毁...

componentWillReceiveProps(nextProps) {//componentWillReceiveProps方法中第一个参数代表即将传入的新的Props
    if (this.props.sharecard_show !== nextProps.sharecard_show){
        //在这里我们仍可以通过this.props来获取旧的外部状态
        //通过新旧状态的对比,来决定是否进行其他方法
        if (nextProps.sharecard_show){
            this.handleGetCard();
        }
    }
}

4 getDerivedStateFromProps
当组件实例化的时候,这个方法替代了componentWillMount(),而当接收到新的 props 时,该方法替代了 componentWillReceiveProps() 和 componentWillUpdate()

触发时机: 会在每次组件被重新渲染前被调用, 这意味着无论是父组件的更新, props 的变化, 或是组件内部执行了 setState(), 它都会被调用.

注意:这个方法是个 static 的方法,因此使用 this 在这个方法中并不指代本实例组件,如果打印出来会发现这个this在这个方法中是null. 而且这个方法会返回值. 当需要更新状态时,需要返回一个 object ,如果不需要任何更新,则返回null即可.
参数: 参数是组件接收到的新的 props 和组件当前的数据. 用户需要在这个函数中返回一个对象, 它将作为 setState() 中的 Updater 更新组件。
注意: componentWillReceiveProps和getDerivedStateFromProps同时存在,控制台报错。

使用时候要异常小心:由于 getDerivedStateFromProps 会在 setState() 后被调用, 并且它的返回值会被用于更新数据. 这意味着你会在 setState() 之后触发 getDerivedStateFromProps, 然后可能意外地再次 "setState()".
getDerivedStateFromProps(nextProps) 函数中的第一个位置参数未必是 "新" 的 props. 在组件内调用了 setState() 时, getDerivedStateFromProps 会被调用. 但是此时的组件其实并没有获得 "新" 的 props, 是的, 这个 nextProps 的值和原来的 props 是一样的.
这就导致了我们在使用 getDerivedStateFromProps 时, 必须添加很多逻辑判断语句来处理 props 上的更新和 state 上的更新, 避免意外地返回了一个 Updater 再次更新数据, 导致数据异常.

5 shouldComponentUpdate

触发时机: 当state或者props改变时候都会触发此生命周期。

可以查看 React性能优化的一些思考 这篇文章使用用法。

6. getSnapshotBeforeUpdate

getSnapshotBeforeUpdate(prevProps, prevState)

与componentWillUpdate不同,getSnapshotBeforeUpdate会在最终的 render 之前被调用,也就是说在getSnapshotBeforeUpdate中读取到的 DOM 元素状态是可以保证与componentDidUpdate中一致的。虽然getSnapshotBeforeUpdate不是一个静态方法,但我们也应该尽量使用它去返回一个值。这个值会随后被传入到componentDidUpdate中,然后我们就可以在componentDidUpdate中去更新组件的状态,而不是在getSnapshotBeforeUpdate中直接更新组件状态。

基本没有用到的地方,所以你懂的。。。

7. componenDtidupdate
注意:1) 将原先写在 componentWillUpdate 中的回调迁移至 componentDidUpdate,将以前放在componenWillReceiveProps中的异步网络请求放在DidUpdate中。
2)在componentDidUpdate中使用setState一定要进行if判断, 判断prevProps, prevState和this.state 之间的一个数据变化, 通过判断变化是否产生, 来决定是否调用if语句里面的state, 尽管在didUpdate里面调用了state, 会再一次进来didUpdate, 但是这一次if语句里面的条件不成立, 就不会再次调用setState造成死循环了。

  state = {
      name: 0
    }
    // old
    componentWillReceiveProps(nextProps) {
      if (nextProps.name !== this.props.name) {
        if (this.state.loginStatus === null) {
          fakeServerRequest(nextProps.name).then(result => {
            // 实例属性 没有挂载在state中
            this.loginStatus = result;
          });
        }
        this.setState({
          name: nextProps.name
        })
      }
    }

    // new
    state = {
      loginStatus: null,
      name: 0
    }
    static getDerivedStateFromProps(nextProps, prevState) {
      if (nextProps.name !== prevState.name) {
         return {
          name: nextProps.name
          loginStatus
        }
      }
      return null;
    }

    // 将以前放在componenWillReceiveProps中的异步网络请求放在DidUpdate中
    componentDidUpdate(nextProps) {
      const { name } = nextProps;
      if (this.state.loginStatus === null) {
        fakeServerRequest(name).then(result => {
          this.setState({ loginStatus: result });
        });
      }
    }

编辑于 2019-05-09

文章被以下专栏收录