生命周期与钩子函数

组件生命周期概述

组件生命周期是 Vue 实例从创建到销毁的完整过程,它通过一系列预定义的钩子函数(lifecycle hooks)允许开发者在特定阶段插入自定义逻辑。理解生命周期有助于开发者精确控制组件的初始化、数据交互、DOM 操作及资源清理,是构建可靠 Vue 应用的基础。

Vue 3 的组件生命周期可分为四个主要阶段:创建阶段(Creation)、挂载阶段(Mounting)、更新阶段(Updating)和卸载阶段(Unmounting),每个阶段对应特定的钩子函数与核心操作。以下为各阶段的详细说明:

  • 创建阶段:组件实例初始化阶段,主要完成数据观测(data observation)、事件绑定等基础设置。此阶段 DOM 尚未生成,无法进行 DOM 操作。

    • beforeCreate:实例初始化后、数据观测前执行,此时 data、methods 等选项未初始化,无法访问组件内部状态。
    • created:实例创建完成后执行,data、methods 等已初始化,可访问组件状态,但 DOM 未生成,适合进行数据初始化(如从本地存储加载数据)。
  • 挂载阶段:组件实例与 DOM 关联的阶段,完成模板编译、DOM 插入等操作,是进行 DOM 相关初始化的关键阶段。

    • beforeMount:模板编译完成、DOM 挂载前执行,此时虚拟 DOM 已创建但未渲染到页面,$el 属性未生成。
    • mounted:DOM 挂载完成后执行,$el 属性指向已渲染的 DOM 元素,适合进行 DOM 操作(如初始化第三方库)、数据请求等需要 DOM 存在的操作。
  • 更新阶段:组件响应数据变化并重新渲染的阶段,当 data 或 props 发生变化时触发。

    • beforeUpdate:数据更新后、DOM 重新渲染前执行,可访问更新前的 DOM 状态,常用于获取更新前的 DOM 信息。
    • updated:DOM 重新渲染完成后执行,可访问更新后的 DOM 状态,需避免在此阶段修改数据(可能导致无限更新循环)。
  • 卸载阶段:组件从 DOM 中移除并销毁的阶段,主要完成资源清理,避免内存泄漏。

    • beforeUnmount:组件卸载前执行,此时组件仍完全可用,适合清理定时器、取消事件监听、终止未完成的网络请求等。
    • unmounted:组件卸载后执行,实例完全销毁,所有事件监听、子组件实例均被移除。

Vue 2 与 Vue 3 生命周期钩子名称对比

  • 创建阶段:beforeCreate(不变)、created(不变)
  • 挂载阶段:beforeMount(不变)、mounted(不变)
  • 更新阶段:beforeUpdate(不变)、updated(不变)
  • 卸载阶段:beforeDestroybeforeUnmountdestroyedunmounted
  • 其他:Vue 2 的 activated/deactivated(keep-alive 相关)在 Vue 3 中保留,组合式 API 中需通过 onActivated/onDeactivated 使用。

常用生命周期钩子实践

在实际开发中,mounted(初始化操作)和 beforeUnmount(资源清理)是最常用的钩子函数。以下通过“数据加载与清理”案例,分别展示 Options API 与 Composition API 两种风格的实现方式,并说明钩子的执行时机与应用场景。

Options API 风格实现

Options API 通过配置对象的属性定义生命周期钩子,适合逻辑简单的组件。以下案例实现一个“数据统计卡片”组件:在组件挂载后请求数据并渲染,同时启动定时器更新统计数据,组件卸载前清理定时器。

javascript
复制代码
<template>
  <div class="stats-card">
    <h3>{{ title }}</h3>
    <p>当前在线人数:{{ onlineUsers }}</p>
    <p>今日访问量:{{ todayViews }}</p>
  </div>
</template>

<script>
export default {
  name: 'StatsCard',
  data() {
    return {
      title: '实时数据统计',
      onlineUsers: 0,
      todayViews: 0,
      updateTimer: null // 存储定时器 ID,用于清理
    }
  },
  // 组件挂载后执行,适合初始化操作(DOM 已生成)
  mounted() {
    // 1. 初始化请求:获取初始数据
    this.fetchStatsData()
    
    // 2. 启动定时器:每 30 秒更新一次数据
    this.updateTimer = setInterval(() => {
      this.fetchStatsData()
    }, 30000)
    
    console.log('组件已挂载,DOM 可访问')
  },
  // 组件卸载前执行,适合资源清理
  beforeUnmount() {
    // 清理定时器,避免组件销毁后继续执行
    clearInterval(this.updateTimer)
    console.log('组件即将卸载,定时器已清理')
  },
  methods: {
    async fetchStatsData() {
      try {
        const response = await fetch('/api/stats')
        const data = await response.json()
        this.onlineUsers = data.onlineUsers
        this.todayViews = data.todayViews
      } catch (error) {
        console.error('数据请求失败:', error)
      }
    }
  }
}
</script>

代码说明

  • mounted 钩子中执行两项关键操作:调用 fetchStatsData 发起初始数据请求,通过 setInterval 启动定时更新。此时组件已挂载到 DOM,可安全进行数据渲染。
  • beforeUnmount 钩子中通过 clearInterval 清理定时器,防止组件卸载后定时器继续运行导致内存泄漏。
  • Options API 将数据(data)、钩子(mounted/beforeUnmount)、方法(methods)分离定义,逻辑分散在不同配置项中。
Composition API 风格实现

Composition API 通过 setup 函数(或 <script setup> 语法糖)组织逻辑,允许将相关代码(如数据请求与清理)聚合在一起,提升代码可维护性。以下为相同功能的 Composition API 实现:

javascript
复制代码
<template>
  <div class="stats-card">
    <h3>{{ title }}</h3>
    <p>当前在线人数:{{ onlineUsers }}</p>
    <p>今日访问量:{{ todayViews }}</p>
  </div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'

// 定义响应式数据
const title = ref('实时数据统计')
const onlineUsers = ref(0)
const todayViews = ref(0)
let updateTimer = null // 非响应式变量,无需用 ref 包裹

// 数据请求函数
const fetchStatsData = async () => {
  try {
    const response = await fetch('/api/stats')
    const data = await response.json()
    onlineUsers.value = data.onlineUsers
    todayViews.value = data.todayViews
  } catch (error) {
    console.error('数据请求失败:', error)
  }
}

// 挂载阶段钩子:组件挂载后执行
onMounted(() => {
  fetchStatsData() // 初始数据请求
  updateTimer = setInterval(fetchStatsData, 30000) // 启动定时器
  console.log('组件已挂载,DOM 可访问')
})

// 卸载阶段钩子:组件卸载前执行
onBeforeUnmount(() => {
  clearInterval(updateTimer) // 清理定时器
  console.log('组件即将卸载,定时器已清理')
})
</script>

代码说明

  • 通过 import 引入 onMountedonBeforeUnmount 等生命周期钩子函数,在 setup 中直接调用并传入回调函数。
  • 数据请求(fetchStatsData)、定时器创建与清理逻辑集中在同一代码块,形成“功能内聚”的逻辑单元,避免 Options API 中“配置项分散”的问题。
  • 响应式数据通过 ref 创建,非响应式变量(如 updateTimer)直接声明,简化状态管理。

组合式 API 中的生命周期钩子

组合式 API 对生命周期钩子进行了函数式封装,统一以 onXxx 命名(如 onMountedonUpdated),需从 vue 导入后在 setup 函数或 <script setup> 中使用。这种设计允许将分散在不同生命周期的相关逻辑聚合,尤其适合复杂组件的代码组织。

核心语法与优势
  • 语法形式:组合式 API 钩子为高阶函数,接收一个回调函数作为参数,回调函数内的逻辑将在对应生命周期阶段执行。例如:

    javascript
    复制代码
    import { onMounted } from 'vue'
    
    setup() {
      onMounted(() => {
        console.log('组件挂载完成')
        // DOM 操作或数据请求逻辑
      })
    }
  • 逻辑聚合:相比 Options API 中钩子函数需分散定义,组合式 API 可将同一功能的“初始化-清理”逻辑放在一起。例如,数据请求与定时器清理可紧邻编写,增强代码可读性与可维护性。

  • 与 setup 的关系setup 函数在 beforeCreatecreated 钩子之间执行,因此无需单独使用这两个钩子,直接在 setup 中编写初始化逻辑即可。

实践案例:用户列表组件

以下通过“用户列表组件”案例,完整展示组合式 API 生命周期钩子的使用流程,涵盖数据加载、渲染、更新与清理的全流程。

javascript
复制代码
<template>
  <div class="user-list">
    <h2>用户列表</h2>
    <div v-if="loading" class="loading">加载中...</div>
    <div v-else-if="error" class="error">{{ error }}</div>
    <ul v-else>
      <li v-for="user in users" :key="user.id">
        {{ user.name }} ({{ user.email }})
      </li>
    </ul>
    <button @click="refreshUsers">刷新列表</button>
  </div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount, onUpdated } from 'vue'

// 状态管理
const users = ref([])
const loading = ref(false)
const error = ref(null)
let pollingInterval = null // 轮询定时器

// 加载用户数据
const loadUsers = async () => {
  loading.value = true
  error.value = null
  try {
    const response = await fetch('/api/users')
    if (!response.ok) throw new Error('请求失败')
    users.value = await response.json()
  } catch (err) {
    error.value = err.message
  } finally {
    loading.value = false
  }
}

// 挂载阶段:初始化数据与轮询
onMounted(() => {
  loadUsers() // 初始加载
  // 启动轮询:每 60 秒自动刷新数据
  pollingInterval = setInterval(loadUsers, 60000)
  console.log('用户列表组件挂载完成')
})

// 更新阶段:监控数据变化
onUpdated(() => {
  console.log(`用户列表更新,当前用户数:${users.value.length}`)
})

// 卸载阶段:清理资源
onBeforeUnmount(() => {
  clearInterval(pollingInterval) // 停止轮询
  console.log('用户列表组件卸载,轮询已停止')
})

// 手动刷新方法
const refreshUsers = () => {
  loadUsers()
}
</script>

案例解析

  1. 状态定义:使用 ref 创建响应式状态 users(用户列表)、loading(加载状态)、error(错误信息),以及非响应式变量 pollingInterval(轮询定时器 ID)。
  2. 数据加载loadUsers 函数封装数据请求逻辑,通过 fetch 获取用户数据,同时管理 loadingerror 状态。
  3. 生命周期钩子
    • onMounted:组件挂载后执行初始数据加载,并启动每 60 秒一次的自动刷新轮询。
    • onUpdated:用户列表数据更新后触发,可用于日志记录或性能监控。
    • onBeforeUnmount:组件卸载前清除轮询定时器,防止内存泄漏。
  4. 用户交互:提供 refreshUsers 方法供按钮调用,支持手动刷新数据,体现生命周期钩子与用户操作的协同。
Options API 与组合式 API 钩子对比
特性 Options API 组合式 API
定义方式 配置项形式(如 mounted: function() {} 函数调用形式(如 onMounted(() => {})
逻辑组织 按生命周期分散在不同配置项 按功能聚合,相关逻辑可紧邻编写
灵活性 固定配置结构,扩展受限 可在任意函数中调用,支持逻辑复用
适用场景 简单组件,逻辑清晰且分散度低 复杂组件,需聚合多生命周期相关逻辑

组合式 API 钩子使用要点

  • 所有 onXxx 钩子需在 setup 执行期间注册,确保回调函数能正确绑定组件实例。
  • 同一钩子可多次调用,回调函数将按注册顺序执行。例如:
    javascript
    复制代码
    onMounted(() => console.log('挂载回调 1'))
    onMounted(() => console.log('挂载回调 2')) // 依次执行
  • 避免在钩子回调中修改响应式数据导致的无限更新(如 onUpdated 中修改 ref 数据)。

总结

Vue 3 生命周期钩子是组件生命周期管理的核心工具,通过创建、挂载、更新、卸载四个阶段的钩子函数,实现对组件行为的精确控制。组合式 API 以函数式方式重构了生命周期钩子,通过 onMountedonBeforeUnmount 等函数,将相关逻辑聚合,解决了 Options API 中代码分散的问题,尤其适合复杂组件的开发。

在实际开发中,需根据组件复杂度选择 API 风格:简单组件可使用 Options API 保持简洁,复杂组件推荐组合式 API 提升逻辑组织性。无论采用哪种方式,均需重视资源清理(如定时器、事件监听),避免内存泄漏,确保应用性能与稳定性。

通过合理运用生命周期钩子,开发者可实现组件从初始化到销毁的全流程可控,为构建健壮、高效的 Vue 应用奠定基础。