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)

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]

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342