React类组件中的生命周期函数,超详细!

往期回顾:

同vue一样,react也是有生命周期的。我们通过下面这个demo来学习react 的生命周期如何在类组件中实现。

react声明周期钩子简介

生命周期函数是React组件中的一些方法,它们在组件的不同状态下被调用,可以在这些函数中实现一些特定的行为。React组件的生命周期可以分为三个阶段:

挂载阶段(Mounting):组件被创建并插入到DOM中

更新阶段(Updating):组件被重新渲染

卸载阶段(Unmounting):组件被从DOM中移除

下面是React中常用的生命周期函数:

  1. constructor(props):初始化组件的状态和属性。
  2. getDerivedStateFromProps(props, state):在组件更新前被调用。返回值将会被添加到组件的状态中。
  3. render():渲染组件。必须返回一个React元素或null。
  4. componentDidMount():组件已经被挂载到DOM中。可以在这个函数中发送请求。
  5. shouldComponentUpdate(nextProps, nextState):组件将要更新。可以在这个函数中判断是否需要更新组件。
  6. getSnapshotBeforeUpdate(prevProps, prevState):在组件更新前被调用。返回值会传递给componentDidUpdate()函数。
  7. componentDidUpdate(prevProps, prevState, snapshot):组件已经更新完毕。可以在这个函数中更新状态或者发送请求。
  8. componentWillUnmount():组件将要被卸载,可以在这个函数中清除定时器或者取消请求。

react的生命周期写钩子使用示例

如图,页面加载时,页面文字透明度逐渐降低,当透明度小于零时,透明度变为1;当点击按钮时,页面卸载。

分析这个页面,我们知道当页面加载时,需要触发文字渐变相关函数;点击按钮实现页面注销。

unmountComponentAtNode

我们先实现页面销毁

js 复制代码
 // 1、创建类组件
  class Life extends React.Component {
    tangPing = () => {
      ReactDOM.unmountComponentAtNode(document.getElementById("test"));
    };
    render() {
      return (
        <div>
          <h1 style={{ opacity: this.state.opacity }}>好好学习,天天向上</h1>
          <button onClick={this.tangPing}>学个锤子</button>
        </div>
      );
    }
  }
  // 渲染组件
  ReactDOM.render(<Life />, document.getElementById("test"));

上述代码中,我们通过ReactDOM.unmountComponentAtNode方法实现了类组件的销毁。unmountComponentAtNode顾名思义,销毁组件在某个节点,因此,其入参就是当前节点id

componentDidMount

componentDidMount是组件挂载到DOM中的钩子,类似于vue中的mount钩子。我们在这个钩子中添加一些逻辑。

js 复制代码
class Life extends React.Component {
  state = {
    opacity: 1,
  };
  tangPing = () => {
    ReactDOM.unmountComponentAtNode(document.getElementById("test"));
  };
  // 组件已经被挂载到DOM中
  componentDidMount() {
    setInterval(() => {
      // 获取原状态
      let { opacity } = this.state;
      // 减少0.1
      opacity -= 0.1;
      // 设置新的透明度
      if (opacity <= 0) opacity = 1;
      this.setState({ opacity });
    }, 200);
  }
  render() {
    return (
      <div>
        <h1 style={{ opacity: this.state.opacity }}>好好学习,天天向上</h1>
        <button onClick={this.tangPing}>学个锤子</button>
      </div>
    );
  }
}

componentDidMount同render函数一样,是类原型上的方法,所以采用componentDidMount() {}的写法而不采用componentDidMount = () =>{}的写法。

页面效果:

可以看出,页面效果实现了,但是,当我们销毁组件时,控制台有报错。

报错原因可以看出来,组件销毁了,setInterval执行时,找不到state状态值了。要解决这个问题,最简单的,就是销毁组件时,清空定时器:

js 复制代码
tangPing = () => {
  clearInterval(this.timer);
  ReactDOM.unmountComponentAtNode(document.getElementById("test"));
};
// 组件已经被挂载到DOM中
componentDidMount() {
  this.timer = setInterval(() => {
    // 获取原状态
    let { opacity } = this.state;
    // 减少0.1
    opacity -= 0.1;
    // 设置新的透明度
    if (opacity <= 0) opacity = 1;
    this.setState({ opacity });
  }, 200);
}

componentWillUnmount

除了用上面的方法清空定时器,我们也可以在页面注销钩子里清空定时器。

componentWillUnmount是组件将要被卸载时的钩子。类似于vue的beforeDestory钩子,可以在这个函数中清除定时器或者取消请求。

js 复制代码
class Life extends React.Component {
  state = {
    opacity: 1,
  };
  tangPing = () => {
    ReactDOM.unmountComponentAtNode(document.getElementById("test"));
  };
  componentWillUnmount() {
    clearInterval(this.timer);
  }
  // 组件已经被挂载到DOM中
  componentDidMount() {
    this.timer = setInterval(() => {
      // 获取原状态
      let { opacity } = this.state;
      // 减少0.1
      opacity -= 0.1;
      // 设置新的透明度
      if (opacity <= 0) opacity = 1;
      this.setState({ opacity });
    }, 200);
  }
  render() {
    return (
      <div>
        <h1 style={{ opacity: this.state.opacity }}>好好学习,天天向上</h1>
        <button onClick={this.tangPing}>学个锤子</button>
      </div>
    );
  }
}

React类组件中旧版生命周期钩子详解

本章我们将讨论react旧版的生命周期钩子函数,以便更好的学习react最新版钩子函数。

react组件挂载时的钩子函数

react组件挂载时的钩子执行顺序如图

我们先看一个简单的求和案例

xml 复制代码
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>hello_react</title>
  </head>
  <body>
    <!-- 准备好一个"容器" -->
    <div id="test"></div>
    <!-- 引入react核心库 -->
    <script type="text/javascript" src="./js/react.development.js"></script>
    <!-- 引入react-dom,用于支持react操作DOM -->
    <script type="text/javascript" src="./js/react-dom.development.js"></script>
    <!-- 引入babel,用于将jsx转为js -->
    <script type="text/javascript" src="./js/babel.min.js"></script>
    <script type="text/babel">
      // 1、创建类组件
      class Count extends React.Component {
        state = {
          count: 0,
        };
        add = () => {
          this.setState({ count: this.state.count + 1 });
        };
        render() {
          return (
            <div>
              <h1>{this.state.count}</h1>
              <button onClick={this.add}>点我+1</button>
            </div>
          );
        }
      }
      // 渲染组件
      ReactDOM.render(<Count />, document.getElementById("test"));
    </script>
  </body>
</html>

页面效果

现在,我们将钩子函数写入上述代码中,进行验证

javascript 复制代码
class Count extends React.Component {
  constructor(props) {
    console.log("Count的constructor函数钩子");
    super(props);
    this.state = {
      count: 0,
    };
  }
  // 组价挂载前
  componentWillMount() {
    console.log("Count的componentWillMount函数钩子");
  }
  add = () => {
    this.setState({ count: this.state.count + 1 });
  };
  render() {
    console.log("Count的render函数钩子");
    return (
      <div>
        <h1>{this.state.count}</h1>
        <button onClick={this.add}>点我+1</button>
      </div>
    );
  }
  // 组价挂载完成
  componentDidMount() {
    console.log("Count的componentDidMount函数钩子");
  }
}

上述代码中,我们使用了constructor,我了代码一致性,我们将state写在了constructor内部。

我们看看钩子的执行顺序

通过上图,我们可以看出组件挂载过程中,钩子函数的执行顺序是:constructor、componentWillMount、render及componentDidMount

注意,render钩子在每次页面有更新时,都会执行一次。

react组件更新时的钩子

当组件有更新时,组件也会触发一系列钩子函数

setState系列钩子

我们先看最简单的setState这条线的钩子

js 复制代码
class Count extends React.Component {
  constructor(props) {
    console.log("Count的constructor函数钩子");
    super(props);
    this.state = {
      count: 0,
    };
  }
  // 组价挂载前
  componentWillMount() {
    console.log("Count的componentWillMount函数钩子");
  }
  add = () => {
    this.setState({ count: this.state.count + 1 });
  };
  render() {
    console.log("Count的render函数钩子");
    return (
      <div>
        <h1>{this.state.count}</h1>
        <button onClick={this.add}>点我+1</button>
      </div>
    );
  }
  // 组价挂载完成
  componentDidMount() {
    console.log("Count的componentDidMount函数钩子");
  }
  // 控制是否允许组件更新的钩子,需要返回一个布尔值
  shouldComponentUpdate() {
    console.log("组件状态更改控制钩子:shouldComponentUpdate");
    return true;
  }
  // 组件更新前
  componentWillUpdate() {
    console.log("组件更新前:ComponentWillUpdate");
  }
  // 组件更新完成
  componentDidUpdate() {
    console.log("组件更新完成:componentDidUpdate");
  }
}

通过下图,当组件挂载时,以此执行constructor、componentWillMount、render、componentDidMount钩子;组件state更新时,依次触发shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate钩子。

注意,shouldComponentUpdate是一个特殊钩子,是控制是否允许更新state状态值的,他需要返回一个布尔值,代表是否更新state的值

forceUpdate系列钩子

当页面有状态更改时,会触发setState系列钩子。但有时候,我们不想更改任何状态值也想上页面更新,这个时候就会触发forceUpdate系列钩子。forceUpdate系列钩子只比setState钩子少一个shouldComponentUpdate生命周期。

我们看一个demo

js 复制代码
class Count extends React.Component {
  constructor(props) {
    console.log("Count的constructor函数钩子");
    super(props);
    this.state = {
      count: 0,
    };
  }
  // 组价挂载前
  componentWillMount() {
    console.log("Count的componentWillMount函数钩子");
  }
  add = () => {
    this.setState({ count: this.state.count + 1 });
  };
  force = () => {
    this.forceUpdate();
  };
  render() {
    console.log("Count的render函数钩子");
    return (
      <div>
        <h1>{this.state.count}</h1>
        <button onClick={this.add}>点我+1</button>
        <button onClick={this.force}>强制更新</button>
      </div>
    );
  }
  // 组价挂载完成
  componentDidMount() {
    console.log("Count的componentDidMount函数钩子");
  }
  // 控制是否允许组件更新的钩子,需要返回一个布尔值
  shouldComponentUpdate() {
    console.log("组件状态更改控制钩子:shouldComponentUpdate");
    return true;
  }
  // 组件更新前
  componentWillUpdate() {
    console.log("组件更新前:ComponentWillUpdate");
  }
  // 组件更新完成
  componentDidUpdate() {
    console.log("组件更新完成:componentDidUpdate");
  }
}

上述代码,我们通过this.forceUpdate()强制更新了页面,以此触发了componentWillUpdate、render、componentDidUpdat钩子

父组件触发render系列钩子

我们实现一个嵌套的父子组件,父组件有两个按钮,子组件用来显示父组件的选中按钮。

js 复制代码
<script type="text/babel">
  // 1、创建类组件
  class Parent extends React.Component {
    state = { text: "" };
    choose = (type) => {
      if (type === "1") this.setState({ text: "父组件选择了按钮一" });
      if (type === "2") this.setState({ text: "父组件选择了按钮二" });
    };
    render() {
      return (
        <div>
          <button
            onClick={() => {
              this.choose("1");
            }}
          >
            按钮1
          </button>
          <button
            onClick={() => {
              this.choose("2");
            }}
          >
            按钮2
          </button>
          <Child text={this.state.text}></Child>
        </div>
      );
    }
  }
  // 渲染组件
  class Child extends React.Component {
    render() {
      const { text } = this.props;
      return <div>{text ? text : "什么都没选中"}</div>;
    }
  }
  ReactDOM.render(<Parent />, document.getElementById("test"));
</script>

上述代码中我们在父组件中定义了按钮选中的状态文本值state.text,然后传递给子组件。子组件通过props接受。

当父组件切换按钮导致子组件props值发生变化时子组件触发一些列钩子

js 复制代码
// 渲染组件
class Child extends React.Component {
  // 父组件触发子组件props更新特有的钩子
  componentWillReceiveProps() {
    console.log("组件接受新的props前:componentWillReceiveProps");
  }
  // 控制是否允许组件更新的钩子,需要返回一个布尔值
  shouldComponentUpdate() {
    console.log("子组件状态更改控制钩子:shouldComponentUpdate");
    return true;
  }
  // 组件更新前
  componentWillUpdate() {
    console.log("子组件更新前:ComponentWillUpdate");
  }
  // 组件更新完成
  componentDidUpdate() {
    console.log("子组件更新完成:componentDidUpdate");
  }
  render() {
    console.log("子组件的render函数钩子");
    const { text } = this.props;
    return <div>{text ? text : "什么都没选中"}</div>;
  }
}

如图,componentWillReceiveProps是子组件props值发生变化时,所特有的钩子。

钩子函数总结

React组件的生命周期可以分为三个阶段:

  1. 挂载阶段(Mounting):组件被创建并插入到DOM中

由ReactDom.render()触发--初次渲染

  • constructor
  • componnetWillMount
  • render
  • componentDidMount(常用)

这个钩子做一些初始化的事情,例如开启定时器、发送请求。订阅消息等

  1. 更新阶段(Updating):组件被重新渲染
  • shouldComponentUpdate
  • componentWillUpdate
  • rende
  • componentDidUpdate
  1. 卸载阶段(Unmounting):组件被从DOM中移除
  • componnetWillUnmount

这个钩子做一些收尾的事情,例如关闭定时器、取消订阅消息等

React类组件中新版生命周期钩子详解

react的最新生命周期和旧版有一些区别,它删除了包含"will"关键词的钩子函数,增加了getDerivedStateFromProps和getSnapshotBeforeUpdate两个新钩子

getDerivedStateFromProps

getDerivedStateFromProps是React 16.3版本引入的一个新的生命周期钩子函数。它的作用是根据新的props和state来派生出一个新的state。这个生命周期函数在组件实例化、接收新的props、和父组件强制更新的时候都会调用。

它的用法类似于旧版的componentWillReceiveProps,但是getDerivedStateFromProps有两个参数:

  • props:最新的props值
  • state:当前组件的state

它的返回值应该是一个对象,代表着组件的新状态。如果不需要更新状态,则返回null。

这个函数在一定程度上能够减少由于componentWillReceiveProps导致的bug,因为它必须返回一个新的state,不能直接修改当前的state。这样做有一个好处,就是可以更好地控制状态的变化流程,增加组件的稳定性。

注:这个钩子几乎不使用

getSnapshotBeforeUpdate

getSnapshotBeforeUpdate 是 React 组件生命周期中的一个钩子函数,它在组件更新 DOM 之前调用,常常用于处理在组件更新后,需要获取 DOM 相关信息的情况。

在每次组件更新时,getSnapshotBeforeUpdate 都会被调用,并且在 render 方法之后被调用。其返回值可以作为 componentDidUpdate 方法的第三个参数传入,从而能够在 componentDidUpdate 中访问到前一时刻的 DOM 信息。

一个典型的 getSnapshotBeforeUpdate 方法的实现可能如下所示:

js 复制代码
class ExampleComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
    this.state = {
      message: "",
    };
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    // 计算更新前的 DOM 高度
    const prevHeight = this.myRef.current.scrollHeight;
    return prevHeight;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // 对比更新前后的 DOM 高度,并更新其它相关状态
    if (snapshot !== null) {
      const currentHeight = this.myRef.current.scrollHeight;
      if (currentHeight > snapshot) {
        this.setState({ message: "Scrolling down" });
      } else if (currentHeight < snapshot) {
        this.setState({ message: "Scrolling up" });
      } else {
        this.setState({ message: "No scrolling" });
      }
    }
  }

  render() {
    return (
      <div ref={this.myRef}>
        {this.props.children}
        <p>{this.state.message}</p>
      </div>
    );
  }
}

在上面的例子中,getSnapshotBeforeUpdate 方法计算了更新前的 DOM 高度并返回。在 componentDidUpdate 方法中,通过比较更新前后的高度,我们可以判断用户是向上滚动还是向下滚动,并更新相应的状态。

小结

生命周期函数是React组件中的一些方法,它们在组件的不同状态下被调用,可以在这些函数中实现一些特定的行为。React组件的生命周期可以分为三个阶段:

  1. 挂载阶段(Mounting):组件被创建并插入到DOM中
  2. 更新阶段(Updating):组件被重新渲染
  3. 卸载阶段(Unmounting):组件被从DOM中移除

下面是React中常用的生命周期函数:

  1. constructor(props):初始化组件的状态和属性。
  2. getDerivedStateFromProps(props, state):在组件更新前被调用。返回值将会被添加到组件的状态中。
  3. render():渲染组件。必须返回一个React元素或null。
  4. componentDidMount():组件已经被挂载到DOM中。可以在这个函数中发送请求。
  5. shouldComponentUpdate(nextProps, nextState):组件将要更新。可以在这个函数中判断是否需要更新组件。
  6. getSnapshotBeforeUpdate(prevProps, prevState):在组件更新前被调用。返回值会传递给componentDidUpdate()函数。
  7. componentDidUpdate(prevProps, prevState, snapshot):组件已经更新完毕。可以在这个函数中更新状态或者发送请求。
  8. componentWillUnmount():组件将要被卸载,可以在这个函数中清除定时器或者取消请求。
相关推荐
也无晴也无风雨1 小时前
深入剖析输入URL按下回车,浏览器做了什么
前端·后端·计算机网络
Martin -Tang1 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
小牛itbull3 小时前
ReactPress:构建高效、灵活、可扩展的开源发布平台
react.js·开源·reactpress
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
阮少年、4 小时前
java后台生成模拟聊天截图并返回给前端
java·开发语言·前端
郝晨妤6 小时前
鸿蒙ArkTS和TS有什么区别?
前端·javascript·typescript·鸿蒙
AvatarGiser6 小时前
《ElementPlus 与 ElementUI 差异集合》Icon 图标 More 差异说明
前端·vue.js·elementui
喝旺仔la6 小时前
vue的样式知识点
前端·javascript·vue.js