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

2020-07-13  本文已影响0人  sherry_碎片

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

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中更合适

componentWillReceiveProps

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

getDerivedStateFromProps

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

需要优化的点:

static getDerivedStateFromProps(props, state) {
    if (props.currentRow !== state.lastRow) {
      return {
        isScrollingDown: props.currentRow > state.lastRow,
        lastRow: props.currentRow,
      };
    }

    // 返回 null 表示无需更新 state。
    return null;
  }

官方指出,如果要执行副作用(数据提取和动画)请改用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 就可以解决这个问题

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]

上一篇下一篇

猜你喜欢

热点阅读