React组件生命周期,各个生命周期可以进行什么操作以及如何使用useEffect模拟组件生命周期

在 React 中,组件的生命周期是指组件从创建、渲染、更新到销毁的完整过程。不同阶段有不同的钩子函数(类组件)或处理方式(函数组件通过useEffect),用于执行特定操作。下面详细说明类组件各生命周期的作用、可执行的操作,以及如何用useEffect在函数组件中模拟这些生命周期。

一、类组件的生命周期及操作

类组件的生命周期分为挂载(Mounting)更新(Updating)卸载(Unmounting) 三个核心阶段,每个阶段有明确的钩子函数和适用场景。

1. 挂载阶段(组件从创建到插入 DOM)

挂载阶段是组件首次被创建并渲染到 DOM 的过程,仅执行一次。

钩子函数 执行时机 可执行的操作
constructor(props) 组件初始化时(最早执行) - 初始化组件状态(this.state)- 绑定事件处理函数(如this.handleClick = this.handleClick.bind(this))- 注意:避免在这里调用setState,也不要执行副作用(如数据请求)
static getDerivedStateFromProps(props, state) 组件实例化后、接收新 props 时(挂载和更新阶段都会触发) - 根据 props 动态更新 state(极少使用,仅用于 "props 直接控制 state" 的场景,如表单受控组件)- 必须返回新的 state 对象或null(不允许有副作用)
render() 挂载和更新阶段都会触发,用于生成 JSX - 渲染组件 UI,返回 JSX 结构- 必须是纯函数(不修改 state、不执行副作用),仅根据 props 和 state 返回 UI
componentDidMount() 组件挂载到 DOM 后(最后执行) - 执行副作用:发起网络请求(如获取初始数据)- 初始化第三方库(如图表库、地图库)- 添加 DOM 事件监听(如窗口大小变化、滚动事件)- 启动定时器(如轮询数据)

2. 更新阶段(组件 props 或 state 变化时)

当组件的propsstate发生变化时,会触发更新流程,可能多次执行。

钩子函数 执行时机 可执行的操作
static getDerivedStateFromProps(props, state) 同挂载阶段,接收新 props 后触发 - 同挂载阶段,根据新 props 更新 state
shouldComponentUpdate(nextProps, nextState) 决定是否需要重新渲染(在render前执行) - 返回true(默认):允许渲染;返回false:跳过渲染(性能优化)- 用于避免不必要的重渲染(如比较新旧 props/state,仅在必要时更新)
render() 同挂载阶段,根据新的 props/state 生成 JSX - 重新渲染 UI(纯函数逻辑)
getSnapshotBeforeUpdate(prevProps, prevState) DOM 更新前执行(在render后、componentDidUpdate前) - 捕获 DOM 更新前的快照(如滚动位置、元素尺寸)- 返回值会作为componentDidUpdate的第三个参数
componentDidUpdate(prevProps, prevState, snapshot) DOM 更新后执行 - 根据 props/state 变化执行副作用:- 当prevProps.id !== this.props.id时,重新请求数据- 更新第三方库(如根据新数据刷新图表)- 恢复 DOM 快照(如滚动到之前记录的位置)- 可调用setState(但需包裹在条件判断中,避免无限循环)

3. 卸载阶段(组件从 DOM 中移除)

仅在组件被销毁时执行一次。

钩子函数 执行时机 可执行的操作
componentWillUnmount() 组件卸载前执行 - 清理副作用,避免内存泄漏:- 移除事件监听(如window.removeEventListener)- 清除定时器 / 计时器(clearInterval/clearTimeout)- 取消未完成的网络请求(如 Axios 的cancelToken)- 销毁第三方库实例(如关闭图表、地图)

二、用useEffect模拟生命周期(函数组件)

函数组件没有类组件的生命周期钩子,但useEffect可以统一处理 "副作用"(如数据请求、事件监听等),通过依赖数组控制执行时机,从而模拟类组件的生命周期。

useEffect的语法:

jsx 复制代码
useEffect(() => {
  // 副作用逻辑(对应类组件的"执行操作")
  
  return () => {
    // 清理函数(对应类组件的"清理操作")
  };
}, [dependencies]); // 依赖数组:控制副作用的执行时机

1. 模拟componentDidMount(挂载后执行一次)

当依赖数组为空([])时,副作用仅在组件挂载后执行一次,清理函数仅在卸载时执行,对应componentDidMountcomponentWillUnmount的组合。

场景:初始化数据请求、添加全局事件监听。

jsx 复制代码
import { useEffect, useState } from 'react';

function UserList() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    // 模拟componentDidMount:挂载后请求数据
    const fetchUsers = async () => {
      const res = await fetch('/api/users');
      const data = await res.json();
      setUsers(data);
    };
    fetchUsers();

    // 模拟componentWillUnmount:卸载前清理(如取消请求)
    return () => {
      // 实际项目中可通过AbortController取消请求
      const controller = new AbortController();
      return () => controller.abort();
    };
  }, []); // 空依赖:仅执行一次

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

2. 模拟componentDidUpdate(props/state 变化时执行)

当依赖数组包含变量(如[propA, stateB])时,副作用会在挂载后执行一次 ,且当依赖项变化时重新执行 ,对应componentDidUpdate

场景:根据 props 变化重新请求数据、更新第三方库。

jsx 复制代码
import { useEffect, useState } from 'react';

function UserDetail({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // 模拟componentDidUpdate:userId变化时重新请求
    const fetchUser = async () => {
      const res = await fetch(`/api/users/${userId}`);
      const data = await res.json();
      setUser(data);
    };
    fetchUser();

    // 清理函数:依赖变化前执行(如取消上一次请求)
    return () => {
      // 取消未完成的请求,避免竞态条件
    };
  }, [userId]); // 依赖userId:仅当userId变化时执行

  return <div>{user ? user.name : '加载中...'}</div>;
}

3. 模拟componentWillUnmount(卸载前清理)

useEffect的清理函数会在组件卸载前依赖项变化前 执行,专门用于清理副作用,对应componentWillUnmount

场景:移除事件监听、清除定时器。

jsx 复制代码
import { useEffect, useState } from 'react';

function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    // 挂载后启动定时器
    const timer = setInterval(() => {
      setCount(prev => prev + 1);
    }, 1000);

    // 模拟componentWillUnmount:卸载前清除定时器
    return () => {
      clearInterval(timer); // 必须清理,否则组件卸载后仍会执行
    };
  }, []); // 空依赖:定时器只启动一次

  return <div>计时:{count}秒</div>;
}

4. 模拟 "每次渲染后执行"(较少用)

若省略依赖数组(useEffect(() => { ... })),副作用会在每次渲染后执行(包括挂载和所有更新),清理函数会在 "下一次渲染前" 执行。

场景:需要跟踪所有渲染变化的特殊场景(谨慎使用,可能导致性能问题)。

jsx 复制代码
useEffect(() => {
  console.log('每次渲染后执行(挂载/更新)');
  
  return () => {
    console.log('下一次渲染前执行(清理上一次副作用)');
  };
}); // 无依赖数组:每次渲染都触发

三、核心区别与总结

  • 类组件:基于 "生命周期阶段" 设计,钩子函数对应固定阶段,逻辑分散在多个方法中。
  • 函数组件(useEffect) :基于 "副作用管理" 设计,一个useEffect可处理多个阶段的逻辑(如同时包含挂载和卸载的操作),更灵活。
类组件生命周期 useEffect 模拟方式 核心作用
componentDidMount useEffect(() => { ... }, []) 初始化副作用(数据请求、事件监听)
componentDidUpdate useEffect(() => { ... }, [dep1, dep2]) 依赖变化时执行副作用
componentWillUnmount useEffect(() => { return () => { ... } }, []) 清理副作用(避免内存泄漏)

实际开发中,推荐使用函数组件 +useEffect,因其更简洁且符合 React 的函数式编程思想。理解 "副作用" 的本质(对组件外部环境的操作),比机械对应生命周期阶段更重要。

相关推荐
wearegogog1235 小时前
基于 MATLAB 的卡尔曼滤波器实现,用于消除噪声并估算信号
前端·算法·matlab
Drawing stars5 小时前
JAVA后端 前端 大模型应用 学习路线
java·前端·学习
品克缤5 小时前
Element UI MessageBox 增加第三个按钮(DOM Hack 方案)
前端·javascript·vue.js
小二·5 小时前
Python Web 开发进阶实战:性能压测与调优 —— Locust + Prometheus + Grafana 构建高并发可观测系统
前端·python·prometheus
小沐°6 小时前
vue-设置不同环境的打包和运行
前端·javascript·vue.js
qq_419854056 小时前
CSS动效
前端·javascript·css
烛阴6 小时前
3D字体TextGeometry
前端·webgl·three.js
桜吹雪7 小时前
markstream-vue实战踩坑笔记
前端
C_心欲无痕7 小时前
nginx - 实现域名跳转的几种方式
运维·前端·nginx
花哥码天下7 小时前
恢复网站console.log的脚本
前端·javascript·vue.js