【react】 组件的生命周期

85 阅读5分钟

React 的生命周期主要分为两种:类组件(Class Components)  和 函数组件(Functional Components with Hooks) 。它们实现生命周期的方式完全不同。


1. 类组件的生命周期(经典方式)

这是传统的生命周期模型,主要分为三个阶段:挂载(Mounting)更新(Updating)  和 卸载(Unmounting)

![alt text](https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/lifecycle.png)

(这是一个非常经典的生命周期图,新版 React 已经废弃了一些方法)

第一阶段:挂载 (Mounting)

组件实例创建并插入到 DOM 中时,会按顺序调用以下方法:

  1. constructor()

    • 作用:组件的构造函数,第一个被执行。

    • 用途

      • 初始化 state。
      • 为事件处理函数绑定 this。
    • 注意:这是唯一可以直接给 this.state 赋值的地方。

  2. static getDerivedStateFromProps(props, state)

    • 作用:在 render 之前调用,无论是初始挂载还是后续更新。
    • 用途:让组件的 state 在 props 变化时更新。它应该返回一个对象来更新 state,或者返回 null 表示不更新。
    • 注意:这是一个静态方法,无法访问 this。它很少被使用,因为通常有更简单的替代方案(如在 render 中直接使用 props,或在 componentDidUpdate 中处理)。
  3. render()

    • 作用必须的方法。用于渲染 UI。
    • 用途:读取 this.props 和 this.state,返回一个 React 元素(通常是 JSX)。
    • 注意:render 函数应该是纯函数,意味着在不修改组件 state 的情况下,每次调用都返回相同的结果。不应在 render 中执行有副作用的操作(如 API 请求)。
  4. componentDidMount()

    • 作用在组件被挂载到 DOM **之后**立即调用。 就是组件的模板(render函数里的jsx)已经从虚拟dom变成真实dom了,可以操作了。比如用queryAllselector获取了(useRef)

    • 用途:这是执行副作用(Side Effects)的绝佳位置。

      • 发起网络请求(API calls)。
      • 添加事件监听器(如 window.addEventListener)。
      • 进行 DOM 操作。

第二阶段:更新 (Updating)

当组件的 props 或 state 发生变化时,会触发更新。组件会重新渲染,并按顺序调用以下方法:

  1. static getDerivedStateFromProps(props, state)

    • (同上)在每次更新前都会被调用。
  2. shouldComponentUpdate(nextProps, nextState)

    • 作用:在 render 之前调用,用于性能优化。
    • 用途:根据 nextProps 和 nextState 判断是否需要重新渲染。默认返回 true。如果返回 false,则后续的 render()、getSnapshotBeforeUpdate() 和 componentDidUpdate() 都不会被调用。
    • 注意:通常使用 React.PureComponent 或 React.memo 来自动处理这个逻辑,而不是手动编写。
  3. render()

    • (同上)如果 shouldComponentUpdate 返回 true,则会再次调用 render。
  4. getSnapshotBeforeUpdate(prevProps, prevState)

    • 作用:在 render 之后,但在 DOM 更新之前调用。
    • 用途:允许你在 DOM 更新前捕获一些信息(如滚动位置)。此方法的返回值将作为第三个参数传递给 componentDidUpdate()。
    • 注意:不常用,但在处理如聊天窗口滚动条等场景时很有用。
  5. componentDidUpdate(prevProps, prevState, snapshot)

    • 作用:在组件更新之后立即调用。

    • 用途

      • 当 props 变化时,执行副作用(如根据新的 props 发起网络请求)。
      • 进行 DOM 操作。
    • 注意:在这里执行副作用时,务必将其包裹在条件语句中,否则可能导致无限循环。例如:if (this.props.userID !== prevProps.userID) { ... }

第三阶段:卸载 (Unmounting)

当组件从 DOM 中被移除时,会调用此方法:

  1. componentWillUnmount()

    • 作用:在组件被卸载和销毁之前调用。

    • 用途:执行必要的清理操作。

      • 清除定时器(clearInterval, clearTimeout)。
      • 移除在 componentDidMount 中添加的事件监听器。
      • 取消网络请求。

2. 函数组件的生命周期(现代方式 - Hooks)

在函数组件中,没有生命周期“方法”,而是通过 Hooks 来模拟生命周期的各个阶段。这种方式更灵活、代码更简洁。

核心是 useEffect Hook,它集 componentDidMount, componentDidUpdate, 和 componentWillUnmount 的功能于一身。

挂载 (Mounting) -> useEffect

模拟 componentDidMount,只需给 useEffect 传入一个空的依赖数组 []。

codeJsx

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

function MyComponent() {
  const [data, setData] = useState(null);

  // 这个 effect 只会在组件首次渲染后运行一次
  useEffect(() => {
    console.log('组件已挂载 (ComponentDidMount)');
    // 在这里发起网络请求
    fetch('api/data')
      .then(res => res.json())
      .then(setData);
    
    // 添加事件监听
    window.addEventListener('resize', handleResize);
  }, []); // 空数组表示不依赖任何 props 或 state,所以只运行一次

  return <div>...</div>;
}

更新 (Updating) -> useEffect

模拟 componentDidUpdate,只需在 useEffect 的依赖数组中放入需要监听的 props 或 state。

codeJsx

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

  // 当 userId prop 发生变化时,这个 effect 会重新运行
  useEffect(() => {
    console.log(`组件已更新,因为 userId 变化了 (ComponentDidUpdate)`);
    // 根据新的 userId 获取数据
    fetch(`api/users/${userId}`)
      .then(res => res.json())
      .then(setUser);
  }, [userId]); // 依赖数组中包含 userId

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

卸载 (Unmounting) -> useEffect 的清理函数

模拟 componentWillUnmount,只需在 useEffect 的回调函数中返回一个清理函数

codeJsx

function Timer() {
  useEffect(() => {
    const timerId = setInterval(() => {
      console.log('Tick');
    }, 1000);

    // 返回一个清理函数
    // 这个函数会在组件卸载前被调用
    return () => {
      console.log('组件将卸载,清理定时器 (ComponentWillUnmount)');
      clearInterval(timerId);
    };
  }, []); // 空数组确保 effect 只运行一次,清理函数也只在卸载时运行

  return <div>Timer is running...</div>;
}

总结与对比

生命周期阶段类组件方法函数组件 Hooks
初始化constructor()useState(), useRef() 等 Hooks 的调用
挂载后 (副作用)componentDidMount()useEffect(() => { ... }, [])
更新后 (副作用)componentDidUpdate()useEffect(() => { ... }, [deps])
卸载前 (清理)componentWillUnmount()useEffect 返回的清理函数 () => { ... }
性能优化shouldComponentUpdate() / PureComponentReact.memo(), useMemo(), useCallback()

核心思想转变

  • 类组件:以时间点(挂载、更新、卸载)来组织代码。
  • 函数组件 (Hooks) :以功能(数据获取、事件监听等)来组织代码。一个 useEffect 负责一个功能的副作用和清理,使得相关逻辑更加内聚。