上节课我们对比了新旧两个版本的生命周期。说了新版的生命周期废弃了几个钩子,但是又新增了几个钩子,那么新增的钩子都是干什么用的呢?这节课我们就来看一看。

回顾

在开始这节课的新内容之前,我们先来对上节课的内容来做一个简单的回顾:

  • 新版本生命周期可以使用旧版本的钩子
  • 新版本中废弃了三个旧版本中的钩子,使用这三个钩子要加UNSAFE前缀
  • componentWillMount
  • componentWillUpdate
  • componentWillReceiveProps
  • 新版本中新增了两个钩子
  • getDerivedStateFromProps
  • getSnapshotBeforeUpdate

以上就是我们上节课的主要知识点,这节课我们就新版本的生命周期中新增的钩子来展开讨论一下。

挂载时

我们之前在学习旧版本生命周期的时候有一个很明确的流程,从挂载到更新再到卸载,由易到难,那么我们现在也是一样,先从挂载时开始看起。我们再来把流程图过一遍:

挂载时可以说是我们最基础而且简单的一步,流程就是,构造器 -> 新增的钩子 -> render -> componentDidMount钩子。在这整个流程中是不是和旧版生命周期唯一的区别就是getDerivedStateFromProps钩子的存在?这个钩子我们没见过,也不明白是什么,而且也是新版本生命周期中新增的两个钩子之一。那么这节课我们就主要来学习一下这个钩子:

getDerivedStateFromProps钩子

从流程图上来看,这个钩子和render一样,是横跨了挂载和更新的。那么这是个什么意思呢?从字面意思上来说就是:从props来获取派生的state?什么意思?再读一遍还是没明白。

先别着急,我们来从案例中来研究一下,先看一下之前案例的代码:

class Count extends React.Component {
  constructor(props) {
    console.log("Count--constructor");
    super(props);
    this.state = { count: 0 };
  }

  componentDidMount() {console.log("Count--componentDidMount");}

  render() {
    console.log("Count--render");
    const { count } = this.state;
    return (
      <div>
        <h2>当前点击 {count} 次</h2>
        <button onClick={this.add}>Add</button>
        <button onClick={this.unmount}>Unmount</button>
        <button onClick={this.force}>Force</button>
      </div>
    );
  }

  add = () => {
    const { count } = this.state;
    this.setState({ count: count + 1 });
  }
  unmount = () => {
    ReactDOM.unmountComponentAtNode(
      document.getElementById("test")
    );
  }
  force = () => {
    this.forceUpdate();
  }
}

我们来看一下,代码中没有什么特别的东西,就还是之前的案例,构造器,renderonClick回调方法和componentDidMount钩子。我们目前没有添加什么其他的复杂的方法在里面,我们来看一下效果是什么样子:

image-20220104100214364

就是和往常一样正常的渲染结果,控制台里面也打印出了整个挂载流程所调用的钩子的执行情况。那么我们其实目前也都还没有写getDerivedStateFromProps钩子的,所以说这个东西到底有没有调用呢?那么我们来写一下看看:

class Count extends React.Component {
  constructor(props) {
    console.log("Count--constructor");
    ...
  }

  componentDidMount() {console.log("Count--componentDidMount");}
  getDerivedStateFromProps(){console.log("Count--getDerivedStateFromProps");}

  render() {
    console.log("Count--render");
    ...
  }

  add = () => {...}
  unmount = () => {...}
  force = () => {...}
}

我们也还是不做什么太复杂的处理,就让他在控制台打印,那我们来看一下结果:

image-20220104110502420

报了一个错误警告出来,而且这个方法并没有被执行,但是没有影响到页面渲染。但是报的错误警告是什么呢?

首先我们来想一想为什么会报错?是钩子的方法名被我写错了吗?不可能吧,如果写错了就相当于我们定义了一个自定义的方法了啊,那么就是另一种可能,就是写得不规范。我们来看报错信息说的都是什么:

getDerivedStateFromProps是定义在实例上的,会被忽略,请把它定义成一个静态方法。那么好,我们就加一个关键字呗,现在我们再看一下效果:

image-20220104111136799

怎么又报错了?但是控制台中好像是已经成功执行了这个钩子。报错信息说必须要返回一个state对象或者是null,但是我们却返回了一个undefined。那么我问一下,什么是state对象?这个就很好理解嘛,我们一个组件的state可不就得是一个对象嘛。

那么我们改一下代码吧,我先返回一个null看看会怎么样。

class Count extends React.Component {
  ...

  getDerivedStateFromProps(){
    console.log("Count--getDerivedStateFromProps");
    return null;
  }

  ...
}

我们来看一下效果:

image-20220104111806997

好像都是正常的,我们先来看一下这些调用顺序对不对,按照顺序依次执行了构造器,getDerivedStateFromPropsrendercomponentDidMount顺序是完全和流程图中一致的。

对功能的影响

到这里我们验证了挂载流程,知道了调用顺序。但是如果我到这里给大家停了,说讲完了,你瞧,我这一课这是什么啊?讲了个寂寞。

但是我们说了这个钩子可以返回两种东西,state对象和null,那我们返回一个state对象又会怎么样呢?

但是什么是state对象?我们知道这是一个对象,但是为啥要叫state对象?因为我们要求这个对象要和我们初始化的state中的key-vale的结构保持一致。就好比我们之前初始化的state{count: 0}那么我们返回的状态对象也得是{count: xxx}。那么我这里返回一个{count: 100},那么我们来看一下效果:

image-20220104112922085

大家看,我们在第一次挂载时并没有点击,页面上渲染的结果自动变成了100,大家可能要说,我明白了,我在这个钩子中可以直接更改我们初始化的state。就目前来看,这么说好像没毛病。

image-20220104113331186

而且在开发工具中来看state确实是100,好像就是这么个意思,但是呢,我们把componentDidUpdate方法加上,看一下我们的功能。

class Count extends React.Component {
  ...

  getDerivedStateFromProps(){
    console.log("Count--getDerivedStateFromProps");
    return {count: 100};
  }
  componentDidUpdate() {console.log("Count--componentDidUpdate");}
  ...
}

image-20220104131302892

我们的功能受到了影响,当我点击Add按钮的时,页面结果不变了,但是控制台中却打印出了componentDidUpdate方法的调用情况。但是为什么更新不了?我们看控制台中除了componentDidUpdate钩子被调用了,还有getDerivedStateFromProps钩子也被调用了。也就是说我们点击Add按钮的时候,其实是更新了,但是更新的状态又一次被getDerivedStateFromProps钩子把state中的值改回了100

那么我们得出一个结论就是,当我们使用getDerivedStateFromProps钩子,并且用该钩子返回一个state对象的话,那么该钩子返回的state将会取代我们初始化的state,而且state将无法更新。

派生

那么可能又有人要问,我们这个到底派生了些什么?这和props到底有什么关系?因为,这个钩子是可以接收参数的,而且接收到的就是props,那么们来把代码这么改一下:

class Count extends React.Component {
  ...

  getDerivedStateFromProps(props){
    console.log("Count--getDerivedStateFromProps");
    return props;
  }

  ...
}
ReactDOM.render(<Count count={100}/>,document.getElementById("test"));  

我们来回忆一下,我是不是在渲染组件标签的时候传入了一个叫count标签属性?在react中的意义是什么?是不是传入了props?那么getDerivedStateFromProps接到的props是不是就是{count: 100},那么好,这个时候props的结构是不是就和我们初始化出来的state对象一模一样?那我们把props当做state对象返回出来是不是就可以去取代我们初始化的state呢?我们来看一下效果:

image-20220104131811420

正常地渲染了页面,展示的是100,说明我们直接return props在这一步是有效的。但是还是那个问题,我们无法更新。这一步也解释了我们是如何从props派生出state

因为大家看我们是不是拿到了一个和我们初始化不同的state?这个state是哪来的?getDerivedStateFromProps钩子返回出来的,这个钩子返回的是什么?返回的是我们传进去的props,也就是说我们通过props来生成了一个state。所以这个state就是派生出的state

使用场景

可能有人要问了,我们用react的时候,当我们更新state那么就会驱动页面更新,但是现在我们用了派生出的state,导致了我们页面没法更新。这一个新东西导致state和派生出的state这两个东西都没有了意义。毕竟我state就是为了驱动页面更新存在的,而派生出的state是通过props派生出来的,那么我直接用props好不好啊?这个钩子存在的意义到底是什么?我们来看一下官方文档给的解释:

image-20220104132943939

官方文档说,这个方法适用于罕见的案例。大家有兴趣可以看一下,因为这个写得有些复杂,我也就不去深入给大家絮烦了。只要记住一个原则,就是后面这句当state的值在任何时候都取决于props时才会用到这个方法。

所以说我们回头再看我们的案例,当我们的getDerivedStateFromProps返回值是一个state对象的时候,state的值将完全取决于getDerivedStateFromProps钩子的返回值,所以说我们的案例中其实是不适合用这个钩子的。

但是官方又说了:

image-20220104135120777

派生state会导致代码冗余且组件难以维护,那么我们在实际开发中用不用这个方法呢?如果没有这方面的需求就不要用,了解即可,除非说有个需求绞尽脑汁,最后好像只有这一个解决方案了,那再用这个方法。

总结

  • 仅当state值在任何时候都完全取决于props是才使用getDerivedStateFromProps钩子
  • 派生state会导致代码冗余且组件难以维护
  • 使用了派生state之后,将无法再正常通过state驱动页面更新

Copyright statement:The articles of this site are all original if there is no special explanation, indicate the source please when you reprint.

Link of this article:https://work.lynchow.com/article/about_get_derived_state_from_props/