React 生命周期机制
React 生命周期机制

React组件生命周期一直是开发者关注的重点之一。随着 React 16.8 引入 Hooks,以及 React 18 带来的并发特性,生命周期的概念逐渐从「固定钩子」演进为「更具声明性和粒度控制的执行机制」。

本篇文章将从以下几个方面带你全面了解 React 的生命周期:

  1. 生命周期概览:Class vs Function
  2. Class 组件生命周期详解
  3. Function 组件生命周期 & Hooks
  4. React 18 并发渲染对生命周期的影响
  5. 实践场景和注意事项

一、生命周期概览:Class vs Function

阶段Class 组件(传统)Function 组件(Hooks)
初始化constructorrendercomponentDidMountuseState, useEffect(..., [])
更新shouldComponentUpdaterendercomponentDidUpdateuseEffect(...) (有依赖)
卸载componentWillUnmountuseEffect 的清理函数
错误捕获componentDidCatch, getDerivedStateFromErroruseErrorBoundary(三方库)或错误边界组件

二、Class 组件生命周期详解(不推荐新项目使用)

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

  static getDerivedStateFromProps(nextProps, prevState) {
    console.log("getDerivedStateFromProps");
    return null;
  }

  componentDidMount() {
    console.log("componentDidMount");
  }

  shouldComponentUpdate(nextProps, nextState) {
    console.log("shouldComponentUpdate");
    return true;
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log("getSnapshotBeforeUpdate");
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    console.log("componentDidUpdate");
  }

  componentWillUnmount() {
    console.log("componentWillUnmount");
  }

  render() {
    console.log("render");
    return <div>{this.state.count}</div>;
  }
}

生命周期阶段说明:

  • 初始化阶段

    • constructor:设置初始状态。
    • getDerivedStateFromProps:由 props 派生 state(极少用)。
    • render:渲染 UI。
    • componentDidMount:挂载完成,可进行 DOM 操作、请求数据。
  • 更新阶段

    • shouldComponentUpdate:控制是否更新。
    • getSnapshotBeforeUpdate:捕捉更新前的 DOM 状态(如滚动位置)。
    • componentDidUpdate:更新完成后的回调。
  • 卸载阶段

    • componentWillUnmount:组件卸载前执行清理操作。
  • 错误处理

    • componentDidCatch:捕捉渲染期间的错误。
    • getDerivedStateFromError:用来降级 UI。

三、Function 组件生命周期 & Hooks

React Hooks 是对生命周期机制的重新设计,核心是用组合取代分阶段

useEffect 替代多个生命周期

import { useEffect, useState } from 'react';

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

  // 相当于 componentDidMount + componentDidUpdate
  useEffect(() => {
    console.log('副作用执行');
    return () => {
      console.log('清理副作用'); // 相当于 componentWillUnmount
    };
  }, [count]); // 依赖变更时执行

  return <button onClick={() => setCount(count + 1)}>{count}</button>;
}

对应关系:

生命周期对应 Hook
componentDidMountuseEffect(() => { ... }, [])
componentDidUpdateuseEffect(() => { ... }, [deps])
componentWillUnmountuseEffect(() => { return () => {...} }, [])
shouldComponentUpdateReact.memo + useCallback/useMemo

四、React 18 的并发特性对生命周期的影响

React 18 引入了 Concurrent Mode(并发模式)自动批处理,这对生命周期的执行顺序产生了影响。

变化点:

  1. Strict Mode 下 useEffect 会执行两次(开发环境)

    // React.StrictMode 会在开发模式下模拟卸载再重建
    useEffect(() => {
      console.log('effect');
      return () => {
        console.log('cleanup');
      };
    }, []);
    

    这是为了提前发现副作用中的问题,生产环境只执行一次

  2. Transition 会延迟副作用执行

    import { startTransition } from 'react';
    
    startTransition(() => {
      setState("new");
    });
    // 此时 useEffect 可能会被延迟执行,提高响应性能
    
  3. 自动批处理

    setCount(1);
    setFlag(true);
    // 在 React 18 中,这两个 setState 会被自动合并,提高性能。
    

五、实际开发场景下的使用建议

✅ 推荐做法(Function 组件)

  • 初始数据拉取 → useEffect(..., [])
  • DOM 事件监听 → useEffect + 清理
  • 定时器、订阅等副作用 → 必须在 useEffect 中清理
  • 页面卸载清理 → useEffect 的返回函数
  • 性能优化 → useMemo, useCallback, React.memo

❌ 避免的误区

  • 多个副作用混在一个 useEffect → 可读性差,拆分多个 effect 更清晰
  • render 中执行副作用 → 会违反 React 的纯函数原则
  • 依赖数组写错或遗漏 → 可使用 eslint-plugin-react-hooks 保障正确性

React 的生命周期机制并不是一成不变的规则,而是伴随 React 架构演进而不断升级的策略。从类组件的阶段式调用,到函数组件中用组合和 Hook 管理副作用,再到 React 18 的并发更新优化,我们应该拥抱变化,理解背后的设计哲学:

副作用是 UI 更新的结果,而非原因。