上节课我们展开讨论了一下forceUpdate,并且对比了forceUpdatesetState的区别,那么这节课我们看说最后一个入口父组件render这个流程。

回顾

在开始新课程之前,我们先来对上节课的几个重要知识点来做一个简单的回顾:

  • forceUpdatesetState少一个shouldComponentUpdate环节
  • forceUpdatesetState的本质区别就是forceUpdate不对state做任何更改
  • forceUpdate不受shouldComponentUpdate方法的限制

以上便是上节课的主要知识点,接下来我们来开始进行这节课的内容。

父组件

看到这个词我们有没有觉得仿佛并不是那么陌生?既然我们用了父组件这个词,那么是不是说明组件之间是可以形成父子关系的?就好像类一样,有父类和子类。

好我们先回到我们的案例当中,在前几节课中我们在Count组件中写了大量的代码,几乎所有生命周期的钩子都已经写进去了。现在我们还在这个组件中来继续测试的话代码量太大,我们就不太方便了。那么我们怎么做?我们重新创建一个组件,比如叫CountSub

class Count extends React.Component {...}

class CountSub extends React.Component {
  render() {
    return (
      <div>
      </div>
    );
  }
}
ReactDOM.render(<CountSub />, document.getElementById("test"));

大家看,我这边定义了一个新的组件CountSub,这样的话我是不是好像根本不会挂载Count组件啊?因为饿根本就没有在ReactDOM.render中做跟Count组件有关的事情。也就是说我们定义了一个Count组件但是压根没有用这个组件。

先别着急,我们再定义一个组件叫Test组件,我们让Test组件和CountSub组件形成父子关系,怎么形成父子关系呢?我们来看代码:

class CountSub extends React.Component {
  render() {
    return (
      <div>
        <div>CountSub</div> 
        <Test />
      </div>
    );
  }
}
class Test extends React.Component {
  render() {
    return (
      <div>Test</div>
    );
  }
}
ReactDOM.render(<CountSub />, document.getElementById("test"));

大家看一下上面这段代码,我是不是在CountSub组件render方法中把Test组件的组件标签写进去了?这是个什么意思呢?这就是在CountSub组件中来使用了Test组件。

组件挂载是个什么流程?

  1. react解析组件标签
  2. react发现组件是个类组件
  3. react实例化这个组件类
  4. react通过组件实例对象调用render方法
  5. render方法返回jsx代码执行结果。

好,就先回忆到这。那么大家想一下,在CountSbu组件的render方法中jsx代码执行的过程中遇到了一个组件标签会怎么办?报错?忽略掉?还是说会进一步去解析组件标签呢?

我们来回想一下jsx的语法规则中的其中一块:

  • jsx中的标签在被渲染时会检查标签首字母
  • 如果jsx中标签首字母小写,那么则将该标签渲染为HTML中同名标签,若无同名标签则会报错。
  • 如果jsx中标签首字母大写,那么react就去渲染对应的组件,若该组件没有定义,则报错。

那么现在我们是不是就知道jsx遇到组件标签会怎么做了吧?那就会直接进一步去渲染这个组件标签所对应的组件。那么我们来看一下上面这段代码是什么效果:

image-20211230145234617

这一下我们同时展示出了CountSub组件和Test组件,同过开发工具中可以看出Test组件的层级是包含在CountSub组件内层的所以说CountSub组件是Test组件的父组件。这样这两个组件的父子关系就形成了。

流程图

那么我们既然已经知道如何来将两个组件形成父子关系了,那么就好办了。来顺着这个流程图继续来理我们的流程。

父子组件的更新案例

class CountSub extends React.Component {
  state = {count: 10};

  render() {
    return (
      <div>
        <div>CountSub组件</div> 
        <button onClick={this.clear}>清空</button>
        <Test count={this.state.count}/>
      </div>
    );
  }

  clear = () => {this.setState({count: 0});};
}

class Test extends React.Component {
  render() {
    return (
        <div>Test 将数量变更为了{this.props.count}</div>
    );
  }
}

我们来看这段代码,我们在CountSub组件中初始化了state,让statecount属性为 10 ,但是我们看一下,我们是不是根本没有在CountSub组件中来展示我们state中的值啊,不过呢我们在CountSub组件中挂载了Test组件,那么我想要让Test组件来展示CountSub组件的state中的值,我们应该怎么做?是不是通过props?所以当我在CountSub组件中挂载Test组件的时候把state的值通过标签属性传入了Test组件,然后在Test组件中通过props来拿到CountSub组件的state中的值并展示出来。

那么好,现在我想要更新了。我给CountSub组件添加一个按钮,并且绑定了onClick回调,只要我一点击按钮就会修改state然后驱动页面更新,页面靠什么更新?是不是重新调用render方法?render方法是不是要返回jsx代码执行结果?jsx代码一执行是不是检查到了有Test组件标签,而且还把state中的值通过标签属性传给了Test组件?那么是不是就要解析Test组件标签?然后就使得整个页面实现了一次更新。我们就是这个流程对不对?好我们来看一下效果:

image-20211230151419088

第一次挂载,因为有初始state,所以传入Test组件的值也就是 10 ,那么我们来点击按钮:

image-20211230151516290

正如我们所预期的那样,页面完成了一次更新。但是我们的目的是看这个玩意儿吗?我们不是要探讨生命周期吗?那么好,来看流程图:

组件更新流程

这个父组件render的入口应该怎么触发呢?和另外两个入口没有什么区别,就是一旦父组件调用了render方法,就会触发这个流程。当触发了这个流程之后就会直接调用componentWillReceiveProps方法。这个方法是什么意思?分开来看,字面上意思就是:组件、即将、收到、属性。

那么我们来问一个问题,这里componentWillReceiveProps方法是那个组件的?是不是Tets组件的?因为我们的案例中是Test组件接收到了props啊,也就是说这个componentWillReceiveProps方法是子组件调用的。

那么我再问大家componentWillReceiveProps方法是在什么时候调用的?是不是在子组件接收到props之前调用的?你看这不是说了嘛,即将收到属性,所以说这个方法是在子组件接收到props之前就会被调用。

那么如果我们在子组件中要是写了componentWillReceiveProps方法的话,会不会被执行呢?我们测试一下:

class CountSub extends React.Component {...}
class Test extends React.Component {
  componentWillReceiveProps(){
    console.log("Test-componentWillReceiveProps");
  }

  render() {...}
}

我们在之前的效果中也看出来了,我们的Test组件已经接收到了props,而componentWillReceiveProps方法还是在接收到props之前被调用,那么我们在Test组件中写了componentWillReceiveProps方法,那是不是一定会被调用?我们来看一下效果:

image-20211230153409861

控制台里面空空如也,没调用啊,这是为什么呢?大概有两个原因:

  1. componentWillReceiveProps方法名称写错了
  2. 真的就没有调用

我们检查了一下,我们的方法名确实没有写错,那么是不是就只剩一个可能性了,确实是没有调用。那么为什么?这里就是一个比较坑的点了,我们最一开始是不是就说了这边的三个入口都是生命周期中组件更新的流程?那么我们第一次打开页面的时候state初始化,有更新吗?是不是没有更新啊?我第一次挂载了CountSub组件,执行了CountSub组件的render方法,这个时候父组件虽然调用了render,但是在render返回结果之前,我们是不是第一次挂载了Test组件?这一步是挂载流程并不是更新流程,所以说不会触发componentWillReceiveProps方法的调用,那么我们这个时候点击一下按钮再看一下效果:

image-20211230153209535

正如我们的预期一样,Test组件中的componentWillReceiveProps方法被调用了。或许大家对这一步会有些不理解,我更新了 state,导致父组件重新调用render方法,那么这一次render执行到Test组件标签的时候,难道不是挂载吗?那我们在Test组件中加上componentDidMount方法来测试一下。

class CountSub extends React.Component {...}
class Test extends React.Component {
  componentWillReceiveProps(){
    console.log("Test-componentWillReceiveProps");
  }
  componentDidMount(){console.log("Test-componentDidMount");}

  render() {...}
}

我来问大家,componentWillMount是在什么时候调用?是不是在组件完成挂载之后立即调用?那么是不是就意味着我们组件每挂载一次,就会被调用一次?那么我们来看一下效果:

image-20211230155027950

第一次挂载,控制台打印出我们的componentDidMount被调用了,那么当我们点击按钮是不是改变了 state?那么CountSub是不是就要重新调用render方法?那么render方法中的Test组件标签是不是就要被重新执行一次?这个时候如果Test组件是被重新挂载了,那么是不是会再打印一次Test-componentDidMount?那么我们看结果吧:

image-20211230155346118

我点击了 12 次按钮,componentWillReceiveProps方法被调用了 12 次,而componentDidMount方法却仅仅只在第一次挂载的时候被调用了。所以说当我们点击按钮的时候是触发的更新流程,而且子组件并没有被重新挂载。

当然这个环节过去了,后面的环节就是我们前两节课说过的了,这里就不在赘述了,大家有遗忘的可以去翻一下前两节课的内容。

以上便是本节课的主要内容

总结

  • 只有当父组件的render方法被重新调用才会触发更新流程
  • 只有当父组件的render方法被重新调用才会触发componentWillReceiveProps的调用
  • 当父组件被重新调用的时候,子组件不会被重新挂载
  • componentWillReceiveProps方法在子组件接收到props之前调用

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/lifecycle_prender_old/