【前端知识】React 组件生命周期:从底层原理到实践场景

React 组件生命周期:从底层原理到实践场景

    • [React 组件生命周期:从底层原理到实践场景](#React 组件生命周期:从底层原理到实践场景)
    • [**一、生命周期的底层架构:Fiber 与调和过程**](#一、生命周期的底层架构:Fiber 与调和过程)
    • [**二、Class 组件生命周期:三阶段详解**](#二、Class 组件生命周期:三阶段详解)
      • [**阶段 1:挂载(Mounting)------ 组件首次渲染到 DOM**](#阶段 1:挂载(Mounting)—— 组件首次渲染到 DOM)
      • [**阶段 2:更新(Updating)------ 组件因 props/state 变化重新渲染**](#阶段 2:更新(Updating)—— 组件因 props/state 变化重新渲染)
      • [**阶段 3:卸载(Unmounting)------ 组件从 DOM 移除**](#阶段 3:卸载(Unmounting)—— 组件从 DOM 移除)
      • [**阶段 4:错误处理(Error Handling)------ 组件渲染出错时**](#阶段 4:错误处理(Error Handling)—— 组件渲染出错时)
    • [**三、Function 组件的生命周期:Hooks 模拟**](#三、Function 组件的生命周期:Hooks 模拟)
    • **四、废弃的生命周期方法与原因**
    • **五、最佳实践**
    • **总结**
    • **相关文章**

React 组件生命周期:从底层原理到实践场景

React 组件的生命周期是指组件从创建、更新到销毁 的过程中,React 自动调用的一系列方法(Class 组件)或钩子函数(Function 组件)。理解生命周期的核心是掌握 React 渲染流程(调度→协调→提交) 中各阶段的触发时机,以及如何在合适的时机执行副作用(如数据请求、DOM 操作、事件订阅)。

一、生命周期的底层架构:Fiber 与调和过程

React 16 引入 Fiber 架构后,生命周期的执行时机与 Fiber 树的构建和更新密切相关。Fiber 是可中断、可恢复的异步渲染单元,将渲染过程分为两个阶段:

  1. 协调阶段(Reconciliation Phase)

    • 构建 Fiber 树(虚拟 DOM 的链表表示),标记需要更新的节点("Diff 算法")。
    • 可中断 (通过 requestIdleCallback 调度),期间执行纯计算逻辑 (如 render 方法)。
  2. 提交阶段(Commit Phase)

    • 将 Fiber 树的变化同步提交到真实 DOM ,执行副作用(如 componentDidMount、DOM 操作)。
    • 不可中断,确保 UI 一致性。

生命周期方法(或 Hooks)的执行时机,本质是 Fiber 树在不同阶段(挂载、更新、卸载)的回调。

二、Class 组件生命周期:三阶段详解

Class 组件的生命周期分为 挂载(Mounting)更新(Updating)卸载(Unmounting)错误处理(Error Handling) 四大阶段。

阶段 1:挂载(Mounting)------ 组件首次渲染到 DOM

当组件实例被创建并插入 DOM 时触发,对应 Fiber 树的初次构建

生命周期方法 执行时机 底层原理 使用场景
constructor(props) 组件实例化时最先执行 ,在 render 前。 初始化 state、绑定事件处理函数(this),避免后续重复执行。 - 初始化 state(如 this.state = { count: 0 }) - 绑定 this(如 this.handleClick = this.handleClick.bind(this)
static getDerivedStateFromProps(props, state) render 执行(挂载和更新阶段均触发),静态方法 (无 this)。 根据 props 派生 state(替代废弃的 componentWillReceiveProps)。 - props 变化时同步更新 state(如表单初始值随 props 变化) - 注意:返回 { stateToUpdate }null(不更新)
render() 协调阶段核心方法,返回 JSX 虚拟 DOM Fiber 树构建的依据,纯函数(无副作用),需返回一致的 UI。 - 渲染组件 UI(JSX) - 禁止在此执行副作用(如 API 请求、DOM 操作)
componentDidMount() 提交阶段执行,DOM 已渲染完成 同步执行副作用(此时 DOM 可用),常用于一次性操作。 - 发起数据请求(fetch/axios) - 订阅事件(如 WebSocket、Redux) - 初始化第三方库(如图表库)

示例:挂载阶段数据请求

jsx 复制代码
class UserProfile extends React.Component {
  constructor(props) {
    super(props);
    this.state = { user: null };
  }

  componentDidMount() {
    // DOM 渲染后请求数据(避免阻塞渲染)
    fetch(`/api/users/${this.props.userId}`)
      .then(res => res.json())
      .then(user => this.setState({ user }));
  }

  render() {
    return this.state.user ? <div>{this.state.user.name}</div> : <div>Loading...</div>;
  }
}

阶段 2:更新(Updating)------ 组件因 props/state 变化重新渲染

当组件的 propsstate 变化时触发,对应 Fiber 树的增量更新

生命周期方法 执行时机 底层原理 使用场景
static getDerivedStateFromProps(props, state) 同挂载阶段,render 执行。 同上,确保 props 变化时 state 同步更新。 同上
shouldComponentUpdate(nextProps, nextState) render 执行,返回 boolean Fiber 协调阶段的"优化开关":返回 false 跳过本次渲染(避免不必要 Diff)。 - 性能优化:对比 nextProps/nextState 与当前值,无变化时返回 false - 注意:默认返回 true(必须谨慎使用)
render() 同挂载阶段,返回更新后的虚拟 DOM。 Fiber 树增量更新,标记变化的节点。 同上
getSnapshotBeforeUpdate(prevProps, prevState) render 后、DOM 更新前执行(提交阶段前置)。 捕获 DOM 更新前的状态(如滚动位置、元素尺寸),返回值传给 componentDidUpdate - 保存滚动位置(如聊天窗口) - 记录 DOM 元素尺寸(如折叠面板高度)
componentDidUpdate(prevProps, prevState, snapshot) 提交阶段执行,DOM 已更新完成 执行更新后的副作用(如基于新 DOM 的操作)。 - 数据请求(如搜索关键词变化后重新请求) - 更新第三方库配置(如图表重绘) - 使用 snapshot 恢复状态(如滚动位置)

示例:更新阶段性能优化与滚动位置保存

jsx 复制代码
class ChatWindow extends React.Component {
  constructor(props) {
    super(props);
    this.messagesEndRef = React.createRef();
  }

  shouldComponentUpdate(nextProps) {
    // 仅当消息列表变化时更新(避免头像等无关变化触发渲染)
    return nextProps.messages !== this.props.messages;
  }

  getSnapshotBeforeUpdate(prevProps) {
    // 保存更新前的滚动位置(若消息新增)
    if (prevProps.messages.length < this.props.messages.length) {
      return this.messagesEndRef.current.scrollHeight;
    }
    return null;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    // 恢复滚动位置(新增消息后自动滚动到底部)
    if (snapshot !== null) {
      this.messagesEndRef.current.scrollTop = snapshot - this.messagesEndRef.current.clientHeight;
    }
  }

  render() {
    return (
      <div ref={this.messagesEndRef}>
        {this.props.messages.map(msg => <div key={msg.id}>{msg.text}</div>)}
      </div>
    );
  }
}

阶段 3:卸载(Unmounting)------ 组件从 DOM 移除

当组件实例被销毁时触发,对应 Fiber 树的节点删除

生命周期方法 执行时机 底层原理 使用场景
componentWillUnmount() 提交阶段执行,DOM 即将移除前 清理副作用(避免内存泄漏)。 - 取消 API 请求(如 AbortController) - 移除事件监听(removeEventListener) - 清除定时器(clearInterval) - 取消订阅(如 Redux unsubscribe

示例:卸载阶段清理副作用

jsx 复制代码
class TimerComponent extends React.Component {
  intervalId = null;

  componentDidMount() {
    this.intervalId = setInterval(() => {
      console.log('Timer tick');
    }, 1000);
  }

  componentWillUnmount() {
    // 清除定时器,避免组件卸载后仍执行
    clearInterval(this.intervalId);
  }

  render() {
    return <div>Timer running...</div>;
  }
}

阶段 4:错误处理(Error Handling)------ 组件渲染出错时

当组件渲染、生命周期或子组件构造函数抛出错误时触发。

生命周期方法 执行时机 底层原理 使用场景
static getDerivedStateFromError(error) 错误发生后渲染备用 UI 前执行(静态方法)。 根据错误更新 state,返回 { hasError: true } 触发备用 UI 渲染。 - 捕获子组件错误,显示降级 UI(如"加载失败,请重试")
componentDidCatch(error, info) 提交阶段执行,错误已发生,可记录错误信息。 执行错误上报(如 Sentry)、日志记录。 - 上报错误到监控系统 - 显示错误详情(仅开发环境)

示例:错误边界处理子组件错误

jsx 复制代码
class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError(error) {
    return { hasError: true }; // 更新 state 触发备用 UI
  }

  componentDidCatch(error, info) {
    // 上报错误到监控系统
    logErrorToService(error, info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      return <h1>Something went wrong.</h1>; // 备用 UI
    }
    return this.props.children;
  }
}

// 使用:包裹可能出错的组件
<ErrorBoundary>
  <UserProfile userId="123" />
</ErrorBoundary>

三、Function 组件的生命周期:Hooks 模拟

Function 组件通过 Hooks 模拟 Class 组件的生命周期,核心是用 useStateuseEffectuseLayoutEffect 等钩子函数对应不同阶段。

Class 生命周期 Function 组件 Hooks 实现 执行时机
constructor + state useState(initialState) 组件首次渲染初始化 state
getDerivedStateFromProps useState + useEffect(监听 props 变化)或自定义 Hook(如 usePropsState props 变化时更新 state(类似 Class 的派生状态)。
shouldComponentUpdate React.memo(Component, areEqual)(浅比较 props)或 useMemo(缓存计算结果) 阻止不必要的渲染(性能优化)。
render 函数组件本体(返回 JSX) 协调阶段执行,返回虚拟 DOM。
componentDidMount useEffect(() => {}, [])(依赖数组为空) 提交阶段执行,DOM 渲染后(异步执行)。
componentDidUpdate useEffect(() => {}, [deps])(依赖数组含变量) 依赖变化时执行(DOM 更新后,异步执行)。
getSnapshotBeforeUpdate useLayoutEffect(同步执行,在 DOM 更新后、绘制前) 捕获 DOM 更新前的状态(同步执行,适合读取 DOM 布局)。
componentWillUnmount useEffect 返回的清理函数(return () => {} 组件卸载前执行(清理副作用)。
componentDidCatch react-error-boundary 库(Function 组件需用第三方库实现错误边界) 捕获子组件错误并执行副作用。

示例:Function 组件模拟生命周期

jsx 复制代码
import { useState, useEffect, useLayoutEffect, useRef } from 'react';

function TimerComponent() {
  const [count, setCount] = useState(0);
  const intervalIdRef = useRef(null);

  // 模拟 componentDidMount:初始化定时器
  useEffect(() => {
    intervalIdRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);

    // 模拟 componentWillUnmount:清理定时器
    return () => clearInterval(intervalIdRef.current);
  }, []); // 依赖为空:仅执行一次(挂载时)

  // 模拟 componentDidUpdate:count 变化时打印日志
  useEffect(() => {
    console.log(`Count updated: ${count}`);
  }, [count]); // 依赖为 count:count 变化时执行

  // 模拟 getSnapshotBeforeUpdate + componentDidUpdate:保存滚动位置
  const scrollRef = useRef(null);
  useLayoutEffect(() => {
    if (scrollRef.current) {
      const prevScrollHeight = scrollRef.current.dataset.prevScrollHeight;
      if (prevScrollHeight) {
        scrollRef.current.scrollTop = scrollRef.current.scrollHeight - prevScrollHeight;
      }
      // 保存当前滚动高度供下次更新使用
      scrollRef.current.dataset.prevScrollHeight = scrollRef.current.scrollHeight;
    }
  });

  return (
    <div ref={scrollRef}>
      <p>Count: {count}</p>
    </div>
  );
}

四、废弃的生命周期方法与原因

React 16.3+ 废弃了 3 个不安全的方法(与 Fiber 异步渲染冲突):

废弃方法 替代方案 废弃原因
componentWillMount constructor + componentDidMount Fiber 异步渲染可能导致多次执行,引发副作用(如数据请求)重复触发。
componentWillReceiveProps static getDerivedStateFromProps 允许在渲染前更新 state,但易引发副作用(如直接修改 state),静态方法更安全。
componentWillUpdate getSnapshotBeforeUpdate + componentDidUpdate 可能在渲染中被中断,导致 DOM 操作不安全(如读取过时 DOM 状态)。

五、最佳实践

  1. 优先使用 Function 组件 + Hooks :更简洁、无 this 绑定问题,生命周期逻辑可通过自定义 Hook 复用(如 useFetch 封装数据请求)。
  2. 副作用放对位置
    • DOM 操作、数据请求放 componentDidMount/useEffect(空依赖);
    • 清理副作用放 componentWillUnmount/useEffect 返回函数。
  3. 性能优化
    • React.memo 避免无关 props 变化触发渲染;
    • useMemo/useCallback 缓存计算结果和函数;
    • 谨慎使用 shouldComponentUpdate(优先让 React 自动优化)。
  4. 错误处理 :用错误边界(ErrorBoundary 组件)捕获子组件错误,避免整个应用崩溃。

总结

React 生命周期的本质是 Fiber 渲染流程在各阶段的回调 ,Class 组件通过固定方法暴露这些回调,Function 组件通过 Hooks 灵活模拟。掌握生命周期的核心是:明确副作用的执行时机(挂载后、更新后、卸载前),并结合底层 Fiber 架构理解"为何废弃某些方法"(异步渲染的安全性)。实际开发中,应根据场景选择合适的方式(Class/Hooks),优先用 Hooks 简化逻辑,同时注意性能优化与错误处理。

相关文章

【前端知识】React简单入门

【前端知识】React进阶-组件模式

【前端知识】React生态你了解多少?

相关推荐
CHU7290352 小时前
定制专属美丽时刻:美容预约商城小程序的贴心设计
前端·小程序
浩~~2 小时前
反射型XSS注入
前端·xss
AwesomeDevin2 小时前
AI时代,我们的任务不应沉溺于与 AI 聊天,🤔 从“对话式编程”迈向“数字软件工厂”
前端·后端·架构
harrain3 小时前
antvG2折线图和区间range标记同时绘制
前端·javascript·vue.js·antv·g2
德育处主任Pro3 小时前
从重复搭建到高效生产,RollCode的H5开发新范式
前端
蜡台3 小时前
SPA(Single Page Application) Web 应用(即单页应用)架构模式 更新
前端·架构·vue·react·spa·spa更新
网络点点滴4 小时前
组件通信-作用域插槽
前端·javascript·vue.js
LZQ <=小氣鬼=>5 小时前
React 图片放大镜组件使用文档
javascript·react.js·前端框架·ecmascript
kyriewen115 小时前
异步编程:从“回调地狱”到“async/await”的救赎之路
开发语言·前端·javascript·chrome·typescript·ecmascript·html5