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 的函数式编程思想。理解 "副作用" 的本质(对组件外部环境的操作),比机械对应生命周期阶段更重要。

相关推荐
JiKun2 小时前
ECMA 2025(ES16) 新特性
前端·javascript
一路上__有你2 小时前
闲来无事,写一篇文章吧!
前端·javascript·vue.js
keep_di2 小时前
05-vue3+ts中axios的封装
前端·vue.js·ajax·typescript·前端框架·axios
JiKun2 小时前
ECMA 2024(ES15) 新特性
前端·javascript
百锦再3 小时前
从 .NET 到 Java 的转型指南:详细学习路线与实践建议
android·java·前端·数据库·学习·.net·数据库架构
i小杨3 小时前
前端埋点(打点)方案
前端·状态模式
前端加油站3 小时前
时间转换那些事
前端·javascript
风清云淡_A3 小时前
【VUECLI】node.js打造自己的前端cli脚手架工具
前端·node.js
YuspTLstar3 小时前
一文掌握Redux-toolkit核心原理
前端·react.js