React16:新的生命周期和state可用阶段

React16:新生命周期的使用

Posted by wang chong on April 16, 2019

React16新增了两个生命周期,并且说要在React17版本废除一些生命周期。

React15的生命周期

React16的生命周期

React16中即将废弃的生命周期有:componentWillount、componentWillReceiveProps、componentWillUpdate

新增的生命周期有:getSnapshotBeforeUpdate、getDerivedStateFromProps

这两个生命周期的出现主要是配合React的调度机制fiber的。

注意使用新的生命周期不可以和即将废弃的生命周期在一起使用,否则会报错。

组件生命周期阶段

React16的生命周期分为三个阶段:render阶段、pre-commit阶和commit阶段。

由上面图可以看出,React16把生命周期阶段分的很清晰

  • Render阶段:纯净且没有副作用。
  • Per-commit阶段:可以读取DOM
  • Commit阶段:可以使用Dom,可以有副作用,安排更新。

新增的生命周期

getDericedStateFromProps

这个生命周期是一个静态方法,这个生命周期的出现主要还是用来替代componentWillReceiveProps。

先来说一下componentWillReceiveProps这个生命周期是当父组件传入子组件的props改变的时候触发,主要用于把父组件传来的props转换成state。它的执行时机在render函数前面,如果我们在componentWillReceiveProps中执行脏操作,很容易就会引发render函数执行多次,造成性能浪费。

还有就是在React15的生命周期中挂载阶段和更新阶段把props转成state不在同一个生命周期中完成(挂载阶段在constructor中把props转换成state,更新阶段在componentWillReceiveProps中把props转换成state),很明显造成了代码冗余。

在render渲染前是不允许有副作用的,很容易就形成了render执行多次。

getDericedStateFromProps这个生命周期可以在上图上看到它的执行时机,它有一下特点:

  1. getDericedStateFromProps生命周期是一个类的静态方法,其中不能使用this,故也就减少了执副作用的机会。
  2. getDerivedStateFromProps会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。
  3. getDerivedStateFromProps生命周期接受两个参数,一个是父组件传过来的props,另一个是当前组件的state。
  4. getDerivedStateFromProps生命周期可以返回一个对象,用于更新state,如果不触发state改变则返回null。
  5. getDerivedStateFromProps不允许执行副作用代码。副作用代码放在componentDidMount生命周期中执行。
static getDerivedStateFromProps(props,state){
    return {
        message: "hello lifycycle"
    };
}

getSnapshotBeforeUpdate

从上面图中可以看出getSnapshotBeforeUpdate执行位置在render函数后和componentDidUpdate前。起初React15的生命周期中是没有Pre-Commit阶段的,React为了保持render的纯(不允许有副作用),所以在render后,commit前加了一个pre-commit阶段,在commit前获取DOM的一些信息。

废除componentWillUpdate我认为也是保持render的纯度。

getSnapshotBeforeUpdate有一下特点

  1. 这个生命周期接受两个参数,一个是props,另一个是state。
  2. 这个生命周期必须要和componentDidUpdate一起使用,否则会警告
  3. 这个生命周期可以有返回值,并且返回值会被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>
    );
  }
}

在上述示例中,重点是从 getSnapshotBeforeUpdate 读取 scrollHeight 属性,因为 “render” 阶段生命周期(如 render)和 “commit” 阶段生命周期(如 getSnapshotBeforeUpdate 和 componentDidUpdate)之间可能存在延迟。

上述是官网对于这个例子中两个生命周期的解释。可以看出getSnapshotBeforeUpdate这个生命周期作用还是很大的。

总结

新生命周期的出现一方面是为了配合React的调度机制fiber,另一方面是为了解决之前生命周期的不足,更好的达到渲染目的,让React更专注于View层的构建。

foreUpdate

默认情况下,当组件的state或props改变时,组件将重新渲染。如果你的render()方法依赖于一些其他的数据,你可以告诉React组件需要通过调用forceUpdate()重新渲染。

调用forceUpdate()会导致组件跳过shouldComponentUpdate(),直接调用render()。这将触发组件的正常生命周期方法,包括每个子组件的shouldComponentUpdate()方法。

forceUpdate就是重新render。有些变量不在state上,当时你又想达到这个变量更新的时候,刷新render;或者state里的某个变量层次太深,更新的时候没有自动触发render。这些时候都可以手动调用forceUpdate自动触发render

React15生命周期中哪些使用state