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

对于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)
  • 挂载阶段 constructor, getDerivedStateFromProps render componentDidMount
  • 更新阶段 getDerivedStateFromProps shouldComonentUpdate render getSnapshotBeforeUpdate componentDidUpdate
  • 错误处理 static getDerivedStateFromError() static getDerivedStateFromError()

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 
{this.state.count}
} //页面最后显示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 (
      
{/* ...contents... */}
); } }

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

你可能感兴趣的:(react生命周期更新前后的知识整理)