美文网首页
react生命周期更新前后的知识整理

react生命周期更新前后的知识整理

作者: sherry_碎片 | 来源:发表于2020-07-13 09:40 被阅读0次

    对于react生命周期的理解,反反复复有很多次不同的理解,我就做个整理,以免每次都进行重新推翻
    按照官网的解释组件的生命周期分成挂载,更新,卸载,以及错误处理的几个流程
    16.3以前的生命周期分成

    • 初始化阶段constuctor,
    • 挂载阶段有componentWillMount,render ,componentDidMount
    • 更新阶段的流程是componentWillReceiveProps,shouldComponentUpdate,componentWillUpdate, render,componentDidUpdate
    • 卸载 阶段componentWillunMount
    • 错误处理 componentDidCatch()
      16.4推出fibber之后,官网也说将在17.0开启async rendering(异步渲染)
      那么render之前的函数都将被执行多次 所以
      16.3以后新增getDerivedStateFromProp将逐渐替代render之前(除shouldComponentUpdate)的生命(componentWillMount,componentWillUpdate, componentWillReceiveProps)

    componentWillMount

    该生命是在组件挂载到dom之前会被调用 只调用一次,官网指明在这里用setstate不会引起组件重新渲染dom,此方法是服务端渲染唯一会调用的生命周期函数

    (我试了下在componentsWillMount直接调用setState。是可以让渲染后拿到最新的state的值,但是此时render只调用一次。就说明这里用setstate不会引起组件重新(第二次)渲染dom。只是在render之前setState已经将需要更新的加入队列了。)这样不算重新触发渲染的更新是没什么意义,而这样的初始化state应该放在constuctor里面

    componentDiDMount

    组件挂载后,(插入到Dom树)之后会被调用。官网建议网络数据请求,最适合放在这里。依赖dom节点的初始化应该放在这个生命周期
    但是不适合直接在这里调用setState(),因为componentDidMount本身处于一次更新中,我们又调用了一次setstate 就会在未来在执行一次render 造成不必要的性能浪费。所以不推荐直接在关于componentDidMount调用setstate。 但是在componentDidMount可以条用接口,在回调中去修改setstate。
    官网也指明说,两次渲染会发生在浏览器更新屏幕之前,但是不推荐。会导致性能问题。

    关于在哪个生命周期发起异步请求获取页面初始数据

    那如果在这里发送异步请求拉去数据并且setState更新数据呢,是不是可以比在componentDidMount减少一次渲染,然后优先提早拿到更新的数据呢?(官网不推荐)

      state={
        count:0
      }
     componentWillMount(){
        console.log('willMount')
        fetch('s.codepen.io')
        .then(res =>{ 
          this.setState({count: 'success'})
          console.log('setdata')
        })
        .catch(err => this.setState({count: 'error'}))
      }
     componentDidMount(){
        console.log('didMount')
      }
    render() {
      console.log('render')
      return <div>{this.state.count}</div>
    }
    //页面最后显示success 打印结果
    // willMount
    // render
    // didMount
    //setdata
    //render
    

    可以看出,render是在componentWillMount执行之后马上就被调用,所以此时由于异步请求还没有拿到数据。等到异步请求拿到数据之后去setState。会重新调用render,总的来说还是进行了两次渲染,异步请求之后的setState还是触发了渲染更新。所以初始需要请求异步数据,放在这里也同样需要render一次“加载中”的空数据状态。总的来说,组件在首次渲染时总是会处于没有异步数据的状态。

    那么为什么建议在componentDiDMount异步获取外部数据呢?

    1、如果是服务端渲染,componentWillMount是唯一会执行的生命周期,如果是服务端渲染,在这里获取数据(发送请求)可能会执行两次。一次是在服务端一次是在客户端
    2、如果在16.4之后增加了fiber,使的整个React的生命周期分成两个阶段,在第一阶段的生命周期是可以被中断的,每次中断之后都会重新执行第一阶段得,而第二阶段不能中断。一旦触发第二阶段,就一定要等到第二阶段执行完毕,componentWillMount在第一阶段,componentDidMount在第二阶段,如果吧请求放在componentWillMount中则可能发送多次请求,

    综上所述所以放在componentDidMount中更合适

    • 关于事件订阅
      一般情况下,如果在componentWillMount 中做订阅外部事件,会在componentWillunMount中取消订阅,但是在服务端渲染的情况下,服务端是不会调用componentWillunMount,所以在服务端订阅事件是会导致内存泄露。
      另一方面听上面的问题一样。在未来开启React异步渲染之后,第一阶段componentWillMount调用之后,组件的渲染还是有可能被其他事物中断的,所以没有办法保证componentWillunMount可以被调用 。。
      componentDidMount不会有这个问题 所以添加订阅也应该在componentDidMount中

    componentWillReceiveProps

    componentWillReceiveProps(nextProps)
    调用时机:只有在父组件重新渲染的时候(就是已挂载的组件接收到新的props之前,(此时this.props访问到的还是渲染之前的props))调用,不管父组件传来的props有没有改变。只要父组件重新渲染都会调用此方法、

    getDerivedStateFromProps

    getDerivedStateFromProps(props,state)
    是静态方法,无权访问组件实例,(即使无法使用this)在state更新或者props更新的时候都会调用组件,就是每次渲染前都会调用,这个与componentWillReceiveProps不同。此方法适用于罕见案例,就是state的值在任何情况下都去取决于props。返回一个对象用来更新state,返回null不更新任何内容

    需要优化的点:

    • 基于 props 更新 state
      一般如果是组件的state的值任何情况下都依赖于props的时候,在16.3以后应该抛弃componentWillReceiveProps,而使用getDerivedStateFromProps返回一个对象用来更新state.
    static getDerivedStateFromProps(props, state) {
        if (props.currentRow !== state.lastRow) {
          return {
            isScrollingDown: props.currentRow > state.lastRow,
            lastRow: props.currentRow,
          };
        }
    
        // 返回 null 表示无需更新 state。
        return null;
      }
    
    • props 更新时获取外部数据,props 更新的副作用
      如果需要更新状态以响应props的更改,则可以通过用this.props 和nextProps进行比较。在挂载的过程中,不会针对初始的props去调用改方法,

    官方指出,如果要执行副作用(数据提取和动画)请改用componentDidUpdate ,在这之前很多时候都会用到redux存放props。如果有props更新引起的副作用。所以就会有

    之前16.3之前大多数用
    class ExampleComponent extends React.Component {
      componentWillReceiveProps(nextProps) {
        if (this.props.isVisible !== nextProps.isVisible) {
          this._loadAsyncData(nextProps.isVisible);
        }
      }
    }
    
     static getDerivedStateFromProps(props, state) {
        // 保存 prevId 在 state 中,以便我们在 props 变化时进行对比。
        // 清除之前加载的数据(这样我们就不会渲染旧的内容)。
        if (props.id !== state.prevId) {
          return {
            externalData: null,
            prevId: props.id,
          };
        }
        // 无需更新 state
        return null;
      }
     componentDidUpdate(prevProps, prevState) {
        if (this.state.externalData === null) {
          this._loadAsyncData(this.props.id);
        }
      }
    

    与 componentWillUpdate 类似,componentWillReceiveProps 可能在一次更新中被多次调用,也就是说写在这里的副作用方法,异步请求,回调函数也有可能会被调用多次,而此时与 componentDidMount 类似,componentDidUpdate 也不存在这样的问题,一次更新中 componentDidUpdate 只会被调用一次,所以官网建议讲 componentDidUpdate 就可以解决这个问题

    • 同样的props更新引起的副作用也应该从componentWillReceiveProps 迁移到componentDidUpdate
    • 以及props更新引起的调用外部回调。也应该从componentWillUpdate迁移至componentDidUpdate

    getSnapshotBeforeUpdate

    getSnapshotBeforeUpdate(prevProps, prevState)
    调用时机:会在最终的render之前被调用,也就是getSnapshotBeforeUpdate中获取到的dom元素状态与componentWillUpdate的是一样的,所以可以用 这个方法代替componentWillUpdate获取组件更改之前捕获一些dom信息(例如:滚动高度、)
    返回的一个值作为componentDidUpdate, 的第三个参数。
    配个componentDidUpdate。覆盖componentWillUpdate的用法

    -优化的点:在更新前记录获取原来的dom节点属性
    在没有这个生命周期之前,一般会利用在componentWillUpdate读取更新前dom元素状态属性,但是在异步渲染中,render阶段的生命周期(如 componentWillUpdate 和 render)和commoit阶段的生命周期“”(componentDidUpdate)可能存在延迟、
    官方提供了下面这个例子

    class ScrollingList extends React.Component {
      constructor(props) {
        super(props);
        this.listRef = React.createRef();
      }
    
      getSnapshotBeforeUpdate(prevProps, prevState) {
        // 我们是否在 list 中添加新的 items ?
        // 捕获滚动​​位置以便我们稍后调整滚动位置。
        if (prevProps.list.length < this.props.list.length) {
          const list = this.listRef.current;
          return list.scrollHeight - list.scrollTop;
        }
        return null;
      }
    
      componentDidUpdate(prevProps, prevState, snapshot) {
        // 如果我们 snapshot 有值,说明我们刚刚添加了新的 items,
        // 调整滚动位置使得这些新 items 不会将旧的 items 推出视图。
        //(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
        if (snapshot !== null) {
          const list = this.listRef.current;
          list.scrollTop = list.scrollHeight - snapshot;
        }
      }
    
      render() {
        return (
          <div ref={this.listRef}>{/* ...contents... */}</div>
        );
      }
    }
    

    参考官方文档
    [https://react.docschina.org/blog/2018/03/27/update-on-async-rendering.html#fetching-external-data]

    相关文章

      网友评论

          本文标题:react生命周期更新前后的知识整理

          本文链接:https://www.haomeiwen.com/subject/ttujcktx.html