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

React 组件生命周期:从新手到进阶的完整指南

你是否曾遇到过组件加载后数据没更新?或者页面刷新时状态错乱?这些问题背后,往往隐藏着对 React 组件生命周期 理解不够深入的原因。作为 React 开发者,掌握组件从创建到销毁的全过程,就像掌握一辆汽车的发动机工作原理——不仅能修车,更能调校性能。

在 React 中,每一个组件都像一个“生命体”,它会经历出生、成长、更新、死亡这几个阶段。这个过程,就是我们常说的 React 组件生命周期。理解它,你就能在合适的时机执行逻辑,避免内存泄漏,优化性能。


什么是 React 组件生命周期?

简单来说,React 组件生命周期 是指一个组件从被创建、渲染、更新到最终被销毁的整个过程。在这个过程中,React 提供了一系列“钩子”(Hook),让你可以在特定时间点插入自定义逻辑。

想象一下,你开了一家咖啡馆,每个顾客(组件)进来点单(渲染)、加糖(更新)、离开(卸载)。你可以在顾客进门时打招呼(componentDidMount),加糖时提醒他(componentDidUpdate),离开时清理桌子(componentWillUnmount)。这些动作,就是生命周期钩子。

在函数组件时代,我们通过 useEffect 来模拟生命周期行为。但在类组件中,这些钩子是显式定义的。虽然现代 React 更推荐函数组件,但理解类组件的生命周期,依然能帮助你更深刻地理解 React 的运行机制。


组件的三个主要阶段:挂载、更新、卸载

React 组件生命周期 可以分为三个大阶段:挂载(Mounting)、更新(Updating)、卸载(Unmounting)。每个阶段都有对应的生命周期方法。

挂载阶段:组件“出生”的过程

当组件第一次被渲染到页面上时,会经历挂载阶段。这个过程就像是一个婴儿第一次来到这个世界。

class Welcome extends React.Component {
  constructor(props) {
    super(props);
    // 1. 初始化状态,这是组件的“基因”
    this.state = { message: 'Hello, World!' };
    console.log('1. 构造函数执行');
  }

  // 2. 组件即将挂载到 DOM 前调用
  componentWillMount() {
    console.log('2. 组件即将挂载');
  }

  // 3. 组件已经挂载到 DOM 后调用
  componentDidMount() {
    console.log('3. 组件已挂载完成');
    // 通常在这里发起网络请求、订阅事件、设置定时器
    // 例如:fetch('/api/data').then(...);
  }

  render() {
    return <h1>{this.state.message}</h1>;
  }
}

⚠️ 注意:componentWillMount 在 React 16.3+ 已被标记为不安全,建议避免使用。componentDidMount 是最常用的挂载钩子。

更新阶段:组件“成长”的过程

当组件的 props 或 state 发生变化时,组件会进入更新阶段。这个过程就像一个人在成长,身体变化,需要适应新环境。

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

  // 1. 当 props 或 state 变化时,React 会调用此方法
  // 返回 true 表示允许更新,false 则阻止
  shouldComponentUpdate(nextProps, nextState) {
    console.log('4. 是否应更新组件?');
    // 举例:只有当 count 变化时才更新
    return nextState.count !== this.state.count;
  }

  // 2. 组件即将更新前调用
  componentWillUpdate(nextProps, nextState) {
    console.log('5. 组件即将更新');
  }

  // 3. 组件更新完成(DOM 已更新)后调用
  componentDidUpdate(prevProps, prevState) {
    console.log('6. 组件已更新');
    // 通常用于:更新 DOM、发起新请求、清理旧数据
    if (prevState.count !== this.state.count) {
      console.log(`计数从 ${prevState.count} 变为 ${this.state.count}`);
    }
  }

  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <div>
        <p>当前计数:{this.state.count}</p>
        <button onClick={this.increment}>增加</button>
      </div>
    );
  }
}

这个例子中,shouldComponentUpdate 用于性能优化——避免不必要的重渲染。componentDidUpdate 是处理更新后逻辑的核心钩子。

卸载阶段:组件“离开”的过程

当组件从页面中移除时,进入卸载阶段。这就像一个人离开咖啡馆,需要收桌、关灯。

class Timer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { seconds: 0 };
    this.timer = null;
  }

  // 在组件挂载后启动定时器
  componentDidMount() {
    console.log('定时器启动');
    this.timer = setInterval(() => {
      this.setState(prevState => ({
        seconds: prevState.seconds + 1
      }));
    }, 1000);
  }

  // 在组件卸载前清除定时器
  componentWillUnmount() {
    console.log('定时器已清除');
    if (this.timer) {
      clearInterval(this.timer);
      this.timer = null;
    }
  }

  render() {
    return <p>已运行:{this.state.seconds} 秒</p>;
  }
}

💡 重要提醒:忘记清除定时器、事件监听或订阅,会导致内存泄漏componentWillUnmount 是清理资源的唯一机会。


函数组件中的替代方案:useEffect

虽然类组件的生命周期方法清晰直观,但现代 React 更推荐使用函数组件 + useEffect

import React, { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);

  // useEffect 接受两个参数:回调函数 和 依赖数组
  useEffect(() => {
    console.log('定时器启动');
    const timer = setInterval(() => {
      setSeconds(s => s + 1);
    }, 1000);

    // 清理函数:组件卸载时执行
    return () => {
      console.log('定时器已清除');
      clearInterval(timer);
    };
  }, []); // 依赖数组为空,表示只在挂载时执行一次

  return <p>已运行:{seconds} 秒</p>;
}

useEffectcomponentDidMountcomponentWillUnmount 的组合。
✅ 依赖数组控制何时执行。
✅ 清理函数替代 componentWillUnmount


生命周期方法对比表

生命周期方法 执行时机 典型用途 是否推荐
constructor 组件创建时 初始化 state ✅ 推荐
render 渲染前 返回 JSX ✅ 推荐
componentDidMount 挂载完成后 发起请求、设置定时器 ✅ 推荐
componentDidUpdate 更新完成后 处理 DOM 变化、发起新请求 ✅ 推荐
componentWillUnmount 卸载前 清理定时器、事件监听 ✅ 推荐
shouldComponentUpdate 更新前 控制是否重新渲染 ✅ 推荐(性能优化)
componentWillMount 挂载前 ⚠️ 不安全,避免使用 ❌ 不推荐
componentWillUpdate 更新前 ⚠️ 不安全,避免使用 ❌ 不推荐

📌 小贴士:函数组件中,useMemouseCallback 也常用于优化更新性能,可与 useEffect 配合使用。


实际项目中的最佳实践

在真实项目中,React 组件生命周期 的应用远不止于“打印日志”。以下是几个关键场景:

  1. 数据加载:在 componentDidMountuseEffect 中发起 API 请求,避免在 render 中直接调用。
  2. 资源管理:定时器、WebSocket、事件监听器必须在 componentWillUnmountuseEffect 的清理函数中清除。
  3. 性能优化:使用 shouldComponentUpdateReact.memo 防止不必要的重渲染。
  4. 副作用处理:任何涉及 DOM 操作、网络请求、订阅等操作,都应放在生命周期钩子或 useEffect 中。

总结:掌握生命周期,写出让用户满意的组件

React 组件生命周期 并不是过时的概念,而是理解 React 运行机制的基石。无论是类组件还是函数组件,掌握“何时做什么”都至关重要。

  • 挂载阶段:初始化状态、发起请求。
  • 更新阶段:判断是否需要更新、处理数据变化。
  • 卸载阶段:清理资源,防止内存泄漏。

当你能准确判断每个生命周期钩子的作用,你就能写出更高效、更健壮的 React 代码。不要只停留在“能跑就行”,而要追求“跑得稳、跑得快”。

记住:组件不是静态的,它是有生命的。 理解它的生命周期,就是赋予它生命力的关键。

从今天开始,检查你的组件是否在 componentWillUnmount 中清理了定时器?是否在 useEffect 中正确使用了依赖数组?这些细节,正是区分“普通开发者”和“专业开发者”的分水岭。