如果将组件比作我们图中的🐜,吊绳比作🐜(组件)的寿命,吊绳左端表示🐜的出生(组件的创建),右端表示🐜的死亡(组件的销毁)。🐜从生到死的过程会触发吊牌上的钩子函数执行,在 React 中我们把组件从创建到销毁的过程中需要触发的钩子函数称之为生命周期函数

React 生命周期

初始化阶段( Initalization )

  • setup props ande state (设置组件的初始化属性和状态)
  • 初始化状态对象

    static defaultProps = {
        name:'计数器'  //初始化默认的属性对象
    复制代码

    初始化属性对象

    constructor(props){
        super(props);
        this.state = {
            number:0
    复制代码

    挂载阶段( Mounting )

    挂载:将虚拟DOM转化为真实DOM的过程

    componentWillMount

    组件挂载之前,在渲染过程中可能会执行多次,不推荐使用

    render

    componentDidMount

    组件挂载之后,永远只会执行一次,推荐在此阶段执行副作用,进行异步操作。比如发 ajax 请求,操作 DOM

    更新阶段( Updation )

    属性更新(props变化)

    componentWillReceiveProps

    组件收到新的属性对象时调用,首次渲染不会触发

    shouldComponentUpdate

    询问组件是否可以更新

  • 参数:新的属性对象
  • 返回: boolean
  • true 允许更新继续向下执行
  • false 不允许更新,停止执行,不会调用之后的生命周期函数
  • componentWillUpdate

    组件更新之前

    render

    根据新的属性对象重新挂载(渲染)组件

    componentDidUpdate

    组件更新完成

    状态更新(state变化)

    shouldComponentUpdate

    询问组件是否可以更新

  • 参数:新的状态对象
  • 返回: boolean
  • true 允许更新继续向下执行
  • false 不允许更新,停止执行,不会调用之后的生命周期函数
  • componentWillUpdate

    组件更新之前

    render

    根据新的状态对象重新挂载(渲染)组件

    componentDilUpdtae

    组件更新完成

    卸载阶段( Unmounting )

    componentWillUnmount

    组件卸载之前调用

    有兴趣的朋友可以根据以下代码进行测试

    import React, { Component } from 'react';
     * 父组件
    class Counter extends Component {
      static defaultProps = { //初始化默认的属性对象
        name:'计数器'
      constructor(props) {
        super(props);
        this.state = { // 初始化默认的状态对象
          number:0
        console.log('1. 父constructor初始化 props and state');
      componentWillMount() {
        console.log('2. 父componentWillMount组件将要挂载');
      componentDidMount() {
        console.log('4. 父componentDidMount组件挂载完成');
      shouldComponentUpdate() {
        console.log('5. 父componentShouldUpdate询问组件是否可以更新');
        return true;
      componentWillUpdate() {
        console.log('6. 父componentWillUpdate组件将要更新');
      componentDidUpdate() {
        console.log('7. 父componentDidUpdate组件更新完成');
      render() {
        console.log('3. 父render渲染,也就是挂载');
        const style = {display:'block'}
        return (
          <div style={{border:'10px solid green',pending:'10px'}}>
            {this.props.name}:{this.state.number}
            <button onClick={this.add} style={style}>+</button>
            {this.state.number %3 !== 0 && <SubCounter number={this.state.number}/>}
          </div>
      add = () => {
        this.setState({
          number:this.state.number+1
     * 子组件
    class SubCounter extends Component {
      static defaultProps = {
        number:10
      componentWillReceiveProps() {
        console.log('1. 子componentWillReceiveProps属性将要发生变化 ');
      shouldComponentUpdate(nextProps, nextState) {
        console.log('2. 子componentShouldUpdate询问组件是否可以更新');
        // 调用此方法的时候会把新的属性对象和新的状态对象传递过来
        if (nextProps.number % 3 === 0) {
          return true;
        return false;
      componentWillUnmount() {
        console.log(' 3.子componentWillUnmount组件将要卸载 ');
      render() {
        return (
          <div style={{border:'10px solid red'}}>
            {this.props.number}
          </div>
    export default Counter;
    复制代码

    新版

    constructor

    初始化属性和状态

    getDerivedStateFromProps

    根据属性对象派生状态对象

  • 参数:新的属性对象,旧的状态对象
  • 用途:在没有这个生命周期函数之前,我们使用的数据来源可能是属性对象,也可能是状态对象,在我们的上文中的 放码过来 阶段就有所体现,我们可以通过这个生命周期函数将属性对象派生到状态对象上,使我们在代码中只通过 this.state.XXX 来绑定我们的数据。示例如下
  • import React, { Component } from 'react';
    class Counter extends Component {
      constructor(props) {
        super(props);
        this.state = {
          number:0
      render() {
        return (
            <p>{this.state.number}</p>
            <button onClick={this.add}>+</button>
            <SubCounter number={this.state.number}/>
          </div>
      add = () => {
        this.setState({
          number:this.state.number+1
    class SubCounter extends Component {
      constructor(props) {
        super(props);
        this.state = {
          number:0
      static getDerivedStateFromProps(nextProps, prevState) {
        if (nextProps.number % 2 === 0) {
          return {number:nextProps.number*2}
        } else {
          return {number:nextProps.number*3}
      render() {
        console.log(this.state);
        return (
            <p>{this.state.number}</p>
          </div>
    export default Counter;
    复制代码

    render

    挂载(渲染)组件

    componentDidMount

    组件挂载(渲染)完成

    getDerivedStateFromProps

    根据属性对象派生状态对象

  • 参数:新的属性对象,旧的状态对象
  • shouldComponentUpdate

    询问组件是否可以更新

  • 参数:新的状态对象
  • 返回: boolean
  • true 允许更新继续向下执行
  • false 不允许更新,停止执行,不会调用之后的生命周期函数
  • render

    根据新的状态对象重新挂载(渲染)组件

    getSnapshotbeforeUpdate

    获取更新前的快照

  • 举例:在我们的日常开发中你一定会遇到这个问题,异步加载资源,当浏览器处于非第一屏的时候并且所在屏之前的模块并没有加载出来,你肯定不希望在所在屏之前的模块加载出来之后浏览器显示窗口仍然处于当前的高度,而是希望仍然处于我们所浏览屏的高度,在没有这个生命周期之前 react 处理这个问题是很棘手的。
  • 未使用 getSnapshotbeforeUpdate

    import React, { Component } from 'react';
    class GetSnapshotBeforeUpdate extends Component {
      constructor(props) {
        super(props);
        this.wrapper = React.createRef();
        this.state = {
          messages:['4','3','2','1','0']
      componentDidMount() {
        setInterval(() => {
          this.setState(() => {
            this.state.messages.unshift(this.state.messages.length);
          }, () => {
              this.setState({
                messages: this.state.messages
        },1000)
      render() {
        let style = {
          height: '100px',
          width: '200px',
          border: '1px solid red',
          overflow:'auto'
        return (
          <ul style={style} ref={this.wrapper}>
              this.state.messages.map((message, index) => <li key={index}>{message}</li>)
    export default GetSnapshotBeforeUpdate;
    

    使用getSnapshotbeforeUpdate

    import React, { Component } from 'react';
    class GetSnapshotBeforeUpdate extends Component {
      getSnapshotBeforeUpdate() {
        // 返回更新内容的高度
        return this.wrapper.current.scrollHeight;
      // 组件更新完毕
      componentDidUpdate(prevProps,prevState,prevScrollHeight) {
        console.log(prevProps,prevState,prevScrollHeight);
        this.wrapper.current.scrollTop = this.wrapper.current.scrollTop +
        (this.wrapper.current.scrollHeight - prevScrollHeight);
    export default GetSnapshotBeforeUpdate;
    

    componentDidUpdate

    组件更新完成

    componentWillUnmount

    组件卸载之前

    数据获取为什么一定要在componentDidMount里面调用

  • constructor

    constructor()中获取数据的话,如果时间太长,或者出错,组件就渲染不出来,整个页面都没法渲染了

  •