React 组件生命周期(手把手讲解)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战(已更新的所有项目都能学习) / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新开坑项目:《Spring AI 项目实战》 正在持续爆肝中,基于 Spring AI + Spring Boot 3.x + JDK 21..., 点击查看 ;
  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观

前言

在 React 开发中,组件的生命周期管理是构建高效、可维护应用的核心技能之一。无论是处理数据初始化、更新状态,还是执行清理操作,开发者都需要对组件的“生命阶段”有清晰的理解。本文将通过通俗易懂的比喻、代码示例和实际场景,系统性地解析 React 组件的生命周期,帮助初学者和中级开发者掌握这一关键概念。


一、组件生命周期的定义与核心价值

1.1 什么是组件生命周期?

可以将 React 组件比作一个“有生命”的对象,它会经历创建、更新、销毁三个阶段。每个阶段对应一系列可被开发者定义的“钩子函数”(lifecycle methods),允许我们在特定时机执行自定义逻辑。例如:

  • 出生阶段:组件被首次渲染到页面时,执行初始化操作(如数据请求);
  • 成长阶段:当组件接收到新 props 或 state 变化时,执行更新逻辑;
  • 死亡阶段:组件被移除时,清理资源(如取消定时器或订阅)。

1.2 生命周期的核心价值

  • 控制渲染流程:确保在合适时机执行副作用(如网络请求、DOM 操作);
  • 优化性能:避免不必要的渲染或资源泄漏;
  • 统一逻辑管理:将业务代码与渲染逻辑分离,提升代码可读性。

二、类组件的生命周期详解(基于 React 16.x 以前版本)

尽管函数组件和 Hooks 已成为主流,但理解类组件的生命周期仍能帮助开发者更好地理解 React 的底层机制。

2.1 类组件的完整生命周期流程

以下是类组件从创建到销毁的典型流程:
| 阶段 | 方法名 | 作用说明 | |--------------|---------------------------------|-----------------------------------| | 初始化阶段 | constructor() | 构造函数,初始化 state 和绑定方法 | | | static getDerivedStateFromProps() | 根据 props 更新 state(已废弃) | | | componentDidMount() | 组件挂载后执行(如数据请求) | | 更新阶段 | getDerivedStateFromProps() | (已废弃) | | | shouldComponentUpdate() | 判断是否需要更新(性能优化) | | | render() | 返回 JSX,决定 UI 结构 | | | getSnapshotBeforeUpdate() | 获取更新前的 DOM 状态 | | | componentDidUpdate() | 更新完成后执行(如滚动到顶部) | | 销毁阶段 | componentWillUnmount() | 组件卸载前清理资源(如取消订阅) |

2.1.1 关键方法的深度解析

示例场景: 一个计数器组件,初始值为 0,每点击一次按钮递增 1。

class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    console.log("1. constructor()");
  }

  componentDidMount() {
    console.log("4. componentDidMount()");
    // 发起网络请求或订阅事件
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log("5. shouldComponentUpdate()");
    return nextState.count % 2 === 0; // 只允许偶数时更新
  }

  componentDidUpdate() {
    console.log("7. componentDidUpdate()");
    // 更新后的操作,如保存数据
  }

  componentWillUnmount() {
    console.log("8. componentWillUnmount()");
    // 清理定时器或取消订阅
  }

  render() {
    console.log("6. render()");
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Increment
        </button>
      </div>
    );
  }
}

执行顺序分析:

  1. constructor():初始化 state 和 props;
  2. render():首次渲染 UI;
  3. componentDidMount():挂载完成后执行,适合发起数据请求;
  4. shouldComponentUpdate():当 state/props 变化时,决定是否触发更新;
  5. render()componentDidUpdate():更新后的逻辑执行。

2.2 生命周期的常见误区

  • 不要在 render() 中直接修改 state:这会导致无限递归更新;
  • componentWillMount 和 componentWillReceiveProps 已废弃:React 16.3 版本后,建议改用构造函数或 getSnapshotBeforeUpdate;
  • 避免在生命周期中使用同步副作用:如直接操作 DOM,应改用 ref 或异步方法。

三、函数组件的生命周期(基于 Hooks 的现代化方案)

随着 React Hooks 的引入,函数组件通过 useEffect 等 Hook 实现了与类组件生命周期的兼容性,同时简化了代码结构。

3.1 useEffect 的核心作用

useEffect 可以替代以下类组件方法:

  • componentDidMount:当依赖数组为空时([]);
  • componentDidUpdate:当依赖变化时触发;
  • componentWillUnmount:通过返回清理函数实现。
function Counter() {
  const [count, setCount] = useState(0);

  // 类似 componentDidMount + componentDidUpdate
  useEffect(() => {
    console.log("Component did mount/update");
    // 发起请求或订阅
    return () => {
      console.log("Cleanup on unmount/update");
      // 取消订阅或清除资源
    };
  }, [count]); // 依赖 count 变化时触发

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

3.2 其他常用 Hook 的生命周期映射

类组件方法对应 Hook适用场景
constructor()useState, useEffect初始化 state 或资源
componentDidMount()useEffect (依赖空数组)组件挂载后执行一次
componentDidUpdate()useEffect (依赖变化)状态/props 变化后执行
componentWillUnmount()useEffect 返回函数组件销毁前清理资源
shouldComponentUpdate()React.memo 或自定义逻辑控制是否跳过渲染

四、实际案例:数据驱动的组件生命周期管理

4.1 场景:用户信息展示组件

需求:当组件首次加载时获取用户数据,当用户 ID 变化时重新请求,并在卸载时清除请求。

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

  useEffect(() => {
    // 1. 组件挂载时执行
    const fetchData = async () => {
      const response = await fetch(`/api/users/${userId}`);
      const data = await response.json();
      setUser(data);
    };

    fetchData();

    // 2. 组件卸载时清理
    return () => {
      // 可在此处取消请求(如使用 AbortController)
    };

    // 3. 依赖 userId,当 userId 变化时重新执行
  }, [userId]);

  return user ? <div>{user.name}</div> : <div>Loading...</div>;
}

4.2 典型问题与解决方案

问题: 当用户快速切换 ID 时,如何避免之前的请求覆盖最新结果?
解决方案: 使用 AbortController 取消未完成的请求:

useEffect(() => {
  let isMounted = true; // 标记组件是否已挂载
  const controller = new AbortController();

  const fetchData = async () => {
    try {
      const response = await fetch(
        `/api/users/${userId}`,
        { signal: controller.signal }
      );
      const data = await response.json();
      if (isMounted) setUser(data);
    } catch (error) {
      if (error.name === "AbortError") return;
      console.error("Fetch failed:", error);
    }
  };

  fetchData();

  return () => {
    isMounted = false;
    controller.abort();
  };
}, [userId]);

五、常见问题与最佳实践

5.1 为什么我的组件没有触发 componentDidUpdate?

  • 检查 shouldComponentUpdate 是否返回 false,阻止了更新流程;
  • 确保 state 或 props 确实发生了变化。

5.2 如何避免内存泄漏?

  • useEffect 的清理函数中取消所有异步操作;
  • 使用 useRef 存储非 state 数据,避免重复创建对象。

5.3 类组件与函数组件的选择建议

  • 选择类组件:需要访问 ref 或兼容旧版库时;
  • 选择函数组件 + Hooks:90% 的场景下更简洁易维护。

结论

掌握 React 组件生命周期是构建复杂应用的基石。无论是通过类组件的完整流程,还是通过 Hooks 的现代化方案,开发者都能通过生命周期方法实现精准的逻辑控制。本文通过代码示例和实际场景,帮助读者理解如何在初始化、更新、销毁阶段执行关键操作,最终写出高效、可维护的 React 代码。

在实际开发中,建议优先使用函数组件和 Hooks,同时结合性能分析工具(如 React Developer Tools)观察组件的渲染流程,逐步优化代码结构。记住,生命周期不是“魔法”,而是一套可预测的执行规则——理解它,就能更好地掌控你的 React 应用。

最新发布