React生命周期01 - 从 React 15 说起

300 阅读5分钟

React的最新大版本已经发到了17了,生命周期想相对于15的版本由于React采用Fiber架构,已经有了很大的变化,但是要了解React声明周期的变迁,我们还是要从React15说起

在 React 15 中,大家需要关注以下几个生命周期方法:

constructor()

componentWillReceiveProps()

shouldComponentUpdate()

componentWillMount()

componentWillUpdate()

componentDidUpdate()

componentDidMount()

render()

componentWillUnmount()

这些生命周期方法是如何彼此串联、相互依存的呢?这里我为你总结了一张大图:

image

我们采用如下demo

import React from "react";

import ReactDOM from "react-dom";

// 定义子组件

class LifeCycle extends React.Component {

  constructor(props) {

    console.log("进入constructor");

    super(props);

    // state 可以在 constructor 里初始化

    this.state = { text: "子组件的文本" };

  }

  // 初始化渲染时调用

  componentWillMount() {

    console.log("componentWillMount方法执行");

  }

  // 初始化渲染时调用

  componentDidMount() {

    console.log("componentDidMount方法执行");

  }

  // 父组件修改组件的props时会调用

  componentWillReceiveProps(nextProps) {

    console.log("componentWillReceiveProps方法执行");

  }

  // 组件更新时调用

  shouldComponentUpdate(nextProps, nextState) {

    console.log("shouldComponentUpdate方法执行");

    return true;

  }



  // 组件更新时调用

  componentWillUpdate(nextProps, nextState) {

    console.log("componentWillUpdate方法执行");

  }

  // 组件更新后调用

  componentDidUpdate(preProps, preState) {

    console.log("componentDidUpdate方法执行");

  }

  // 组件卸载时调用

  componentWillUnmount() {

    console.log("子组件的componentWillUnmount方法执行");

  }

  // 点击按钮,修改子组件文本内容的方法

  changeText = () => {

    this.setState({

      text: "修改后的子组件文本"

    });

  };

  render() {

    console.log("render方法执行");

    return (

      <div className="container">

        <button onClick={this.changeText} className="changeText">

          修改子组件文本内容

        </button>

        <p className="textContent">{this.state.text}</p>

        <p className="fatherContent">{this.props.text}</p>

      </div>

    );

  }

}

// 定义 LifeCycle 组件的父组件

class LifeCycleContainer extends React.Component {



  // state 也可以像这样用属性声明的形式初始化

  state = {

    text: "父组件的文本",

    hideChild: false

  };

  // 点击按钮,修改父组件文本的方法

  changeText = () => {

    this.setState({

      text: "修改后的父组件文本"

    });

  };

  // 点击按钮,隐藏(卸载)LifeCycle 组件的方法

  hideChild = () => {

    this.setState({

      hideChild: true

    });

  };

  render() {

    return (

      <div className="fatherContainer">

        <button onClick={this.changeText} className="changeText">

          修改父组件文本内容

        </button>

        <button onClick={this.hideChild} className="hideChild">

          隐藏子组件

        </button>

        {this.state.hideChild ? null : <LifeCycle text={this.state.text} />}

      </div>

    );

  }

}

ReactDOM.render(<LifeCycleContainer />, document.getElementById("root"));

这段代码渲染的结果为 image

可以看到,在组建的初始化阶段,打印依次为:

"进入constructor"
"componentWillMount方法执行"
"render方法执行"
"componentDidMount方法执行"

Mounting 阶段:组件的初始化渲染(挂载)

挂载过程在组件的一生中仅会发生一次,在这个过程中,组件被初始化,然后会被渲染到真实 DOM 里,完成所谓的“首次渲染”,componentWillMount、componentDidMount 方法同样只会在挂载阶段被调用一次 在挂载阶段,一个 React 组件会按照顺序经历如下图所示的生命周期:

组件挂载初次挂载过程

注意 render 在执行过程中并不会去操作真实 DOM(也就是说不会渲染),它的职能是把需要渲染的内容返回出来。真实 DOM 的渲染工作,在挂载阶段是由 ReactDOM.render 来承接的。

componentDidMount 方法在渲染结束后被触发,此时因为真实 DOM 已经挂载到了页面上,我们可以在这个生命周期里执行真实 DOM 相关的操作。此外,类似于异步请求、数据初始化这样的操作也大可以放在这个生命周期来做(侧面印证了 componentWillMount 真的很鸡肋)。

Updating 阶段:组件的更新

Updating阶段 组件的更新分为两种:一种是由父组件更新触发的更新;另一种是组件自身调用自己的 setState 触发的更新。这两种更新对应的生命周期流程如上图,从图中你可以明显看出,父组件触发的更新和组件自身的更新相比,多出了这样一个生命周期方法:componentWillReceiveProps(nextProps)

在这个生命周期方法里,nextProps 表示的是接收到新 props 内容,而现有的 props (相对于 nextProps 的“旧 props”)我们可以通过 this.props 拿到,由此便能够感知到 props 的变化。

从字面意义上理解:componentWillReceiveProps 很容易理解为父组件传入的props变化时候触发的,in fact,这个理解是错误的,准确来说

==如果父组件导致组件重新渲染,即便props没有改变,也会调用此方法== ----

++componentReceiveProps 并不是由 props 的变化触发的,而是由父组件的更新触发的,即便你修改了父组件中一个跟子组件无关的state,componentWillReceiveProps同样会执行!!!++

componentWillUpdate 会在 render 前被触发,它和 componentWillMount 类似,允许你在里面做一些不涉及真实 DOM 操作的准备工作;而 componentDidUpdate 则在组件更新完毕后被触发,和 componentDidMount 类似,这个生命周期也经常被用来处理 DOM 操作。此外,我们也常常将 componentDidUpdate 的执行作为子组件更新完毕的标志通知到父组件。

这里引出了React性能优化的一点: 采用shouldComponentdUpdate进行比较

shouldComponentUpdate(nextProps, nextState)

render 方法由于伴随着对虚拟 DOM 的构建和对比,过程可以说相当耗时。而在 React 当中,很多时候我们会不经意间就频繁地调用了 render。为了避免不必要的 render 操作带来的性能开销,React 为我们提供了 shouldComponentUpdate 这个口子。

React 组件会根据 shouldComponentUpdate 的返回值,来决定是否执行该方法之后的生命周期,进而决定是否对组件进行re-render(重渲染)。shouldComponentUpdate 的默认值为 true,也就是说“无条件 re-render”。在实际的开发中,我们往往通过手动往 shouldComponentUpdate 中填充判定逻辑,或者直接在项目中引入 PureComponent 等最佳实践,来实现“有条件的 re-render”。

Unmounting 阶段:组件的卸载

组件卸载 我们点击“隐藏子组件”后就可以把 LifeCycle 从父组件中移除掉,进而实现卸载的效果

这个生命周期本身不难理解,我们重点说说怎么触发它。组件销毁的常见原因有以下两个

1.组件在父组件中被移除了

2.组件中设置了 key 属性,父组件在 render 的过程中,发现 key 值和上一次不一致,那么这个组件就会被干掉。