12Vue3 生命周期钩子详解

一、生命周期概述

1.1 什么是生命周期

Vue组件从创建到销毁的整个过程,Vue在关键时刻提供钩子函数,允许我们在特定阶段执行自定义逻辑。

1.2 生命周期图示

text
创建阶段:
setup() → onBeforeMount() → onMounted()

更新阶段:
onBeforeUpdate() → onUpdated()

卸载阶段:
onBeforeUnmount() → onUnmounted()

错误处理:
onErrorCaptured() → onRenderTracked() → onRenderTriggered()

二、Composition API 生命周期钩子

2.1 导入和使用

import {
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted,
  onErrorCaptured,
  onActivated,
  onDeactivated,
  onRenderTracked,
  onRenderTriggered,
  onServerPrefetch
} from 'vue'

2.2 完整示例

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

const count = ref(0)
let timer = null

// 组件挂载后执行
onMounted(() => {
  console.log('组件已挂载到DOM')
  
  // 启动定时器
  timer = setInterval(() => {
    count.value++
  }, 1000)
  
  // DOM操作
  const el = document.getElementById('my-element')
  if (el) {
    el.style.color = 'red'
  }
  
  // API请求
  fetchData()
})

// 组件卸载前执行
onUnmounted(() => {
  console.log('组件即将卸载')
  
  // 清理定时器
  if (timer) {
    clearInterval(timer)
    timer = null
  }
  
  // 取消事件监听
  window.removeEventListener('resize', handleResize)
})

async function fetchData() {
  // 数据获取逻辑
}
</script>

三、各个钩子的详细用法

3.1 创建阶段

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

const data = ref(null)

// setup() 阶段 - Composition API的入口
console.log('1. setup() 执行 - 响应式数据初始化')

// 组件挂载到DOM之前
onBeforeMount(() => {
  console.log('2. onBeforeMount() - DOM尚未创建')
  // 此时无法访问DOM元素
  // 适合执行最后的初始化配置
})

// 组件挂载到DOM之后
onMounted(() => {
  console.log('3. onMounted() - DOM已创建')
  console.log('组件根元素:', document.querySelector('#app'))
  
  // 可以访问DOM
  const element = document.querySelector('.my-class')
  if (element) {
    element.style.color = 'blue'
  }
  
  // 适合执行:
  // 1. DOM操作
  // 2. 异步数据请求
  // 3. 事件监听
  // 4. 定时器
  window.addEventListener('resize', handleResize)
})
</script>

<template>
  <div ref="container">内容</div>
</template>

3.2 更新阶段

<script setup>
import { ref, watch, onBeforeUpdate, onUpdated } from 'vue'

const count = ref(0)
const updateCount = ref(0)

// 数据更新前执行(DOM更新前)
onBeforeUpdate(() => {
  console.log('4. onBeforeUpdate() - 数据变化,DOM更新前')
  console.log('当前count:', count.value)
  console.log('DOM中的count:', document.querySelector('#count').textContent)
  
  // 此时DOM还是旧的状态
  // 适合获取更新前的DOM状态
  const oldHeight = document.querySelector('#container').offsetHeight
  console.log('更新前高度:', oldHeight)
})

// 数据更新后执行(DOM更新后)
onUpdated(() => {
  console.log('5. onUpdated() - DOM更新完成')
  console.log('DOM中的count:', document.querySelector('#count').textContent)
  
  // 此时DOM已更新
  // 适合执行依赖新DOM的操作
  const newHeight = document.querySelector('#container').offsetHeight
  console.log('更新后高度:', newHeight)
  
  // 注意:避免在这里修改响应式数据,可能导致无限循环
  updateCount.value++
})

// 手动触发更新
function increment() {
  count.value++
}
</script>

<template>
  <div id="container">
    <p id="count">{{ count }}</p>
    <button @click="increment">增加</button>
    <p>更新次数: {{ updateCount }}</p>
  </div>
</template>

3.3 卸载阶段

<!-- 父组件 -->
<script setup>
import { ref, shallowRef } from 'vue'
import ChildComponent from './ChildComponent.vue'

const showChild = ref(true)
const childKey = ref(1)

function toggleChild() {
  showChild.value = !showChild.value
}

function remountChild() {
  showChild.value = false
  setTimeout(() => {
    childKey.value++
    showChild.value = true
  }, 100)
}
</script>

<template>
  <button @click="toggleChild">切换子组件</button>
  <button @click="remountChild">重新挂载</button>
  
  <ChildComponent v-if="showChild" :key="childKey" />
</template>

<!-- 子组件 ChildComponent.vue -->
<script setup>
import { onBeforeUnmount, onUnmounted } from 'vue'

// 组件卸载前
onBeforeUnmount(() => {
  console.log('6. onBeforeUnmount() - 组件即将卸载')
  // 此时组件实例仍然可用
  // 适合清理不依赖DOM的资源
})

// 组件卸载后
onUnmounted(() => {
  console.log('7. onUnmounted() - 组件已卸载')
  // 此时组件实例已销毁
  // 适合执行:
  // 1. 清理定时器
  // 2. 取消事件监听
  // 3. 清除缓存
  // 4. 取消网络请求
})
</script>

3.4 错误处理钩子

<script setup>
import { onErrorCaptured } from 'vue'

// 捕获后代组件的错误
onErrorCaptured((error, instance, info) => {
  console.error('8. onErrorCaptured() - 捕获到错误')
  console.error('错误对象:', error)
  console.error('组件实例:', instance)
  console.error('错误信息:', info)
  
  // 可以在此处:
  // 1. 错误上报
  // 2. 显示错误界面
  // 3. 阻止错误继续传播(返回false)
  
  // 返回false阻止错误向上传播
  return false
})

// 开发调试钩子
import { onRenderTracked, onRenderTriggered } from 'vue'

// 追踪响应式依赖
onRenderTracked((event) => {
  console.log('依赖被追踪:', event)
})

// 响应式触发重新渲染
onRenderTriggered((event) => {
  console.log('重新渲染被触发:', event)
})
</script>

3.5 KeepAlive 缓存组件钩子

<script setup>
import { onActivated, onDeactivated } from 'vue'

// 当被 <KeepAlive> 缓存的组件激活时
onActivated(() => {
  console.log('组件被激活 (activated)')
  // 恢复组件状态
  // 重新启动定时器
  // 重新获取数据
})

// 当被 <KeepAlive> 缓存的组件停用时
onDeactivated(() => {
  console.log('组件被停用 (deactivated)')
  // 保存组件状态
  // 暂停定时器
  // 清理临时数据
})
</script>

3.6 SSR 服务端渲染钩子

<script setup>
import { onServerPrefetch } from 'vue'

const data = ref(null)

// 服务端数据预取
onServerPrefetch(async () => {
  console.log('服务端预取数据')
  data.value = await fetchServerData()
})

async function fetchServerData() {
  // 服务端数据获取逻辑
  return await fetch('/api/data').then(res => res.json())
}
</script>

四、Options API 生命周期钩子

4.1 对比表

 
Composition APIOptions API执行时机
setup() beforeCreate() 实例初始化前
setup() created() 实例创建完成
onBeforeMount() beforeMount() 挂载前
onMounted() mounted() 挂载后
onBeforeUpdate() beforeUpdate() 更新前
onUpdated() updated() 更新后
onBeforeUnmount() beforeDestroy() 销毁前
onUnmounted() destroyed() 销毁后
onErrorCaptured() errorCaptured() 错误捕获

五、实际应用场景

5.1 数据请求最佳实践

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

const data = ref(null)
const loading = ref(false)
let abortController = null

onMounted(async () => {
  await fetchData()
})

onUnmounted(() => {
  // 组件卸载时取消请求
  if (abortController) {
    abortController.abort()
  }
})

async function fetchData() {
  // 取消之前的请求
  if (abortController) {
    abortController.abort()
  }
  
  abortController = new AbortController()
  loading.value = true
  
  try {
    const response = await fetch('/api/data', {
      signal: abortController.signal
    })
    data.value = await response.json()
  } catch (error) {
    if (error.name !== 'AbortError') {
      console.error('请求失败:', error)
    }
  } finally {
    loading.value = false
  }
}
</script>

5.2 第三方库集成

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'

const chartRef = ref(null)
let chartInstance = null

onMounted(() => {
  // 初始化图表
  if (chartRef.value) {
    chartInstance = echarts.init(chartRef.value)
    updateChart()
  }
  
  // 监听窗口大小变化
  window.addEventListener('resize', handleResize)
})

onUnmounted(() => {
  // 销毁图表实例
  if (chartInstance) {
    chartInstance.dispose()
    chartInstance = null
  }
  
  // 移除事件监听
  window.removeEventListener('resize', handleResize)
})

function handleResize() {
  if (chartInstance) {
    chartInstance.resize()
  }
}

function updateChart() {
  if (chartInstance) {
    chartInstance.setOption({
      // 图表配置
    })
  }
}
</script>

<template>
  <div ref="chartRef" style="width: 600px; height: 400px;"></div>
</template>

5.3 性能优化技巧

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

// 使用 shallowRef 减少不必要的响应式
const largeData = shallowRef({/* 大数据对象 */})

onMounted(() => {
  // 使用 requestAnimationFrame 优化DOM操作
  requestAnimationFrame(() => {
    // DOM操作
  })
  
  // 防抖处理频繁事件
  window.addEventListener('scroll', debounce(handleScroll, 100))
})

onUnmounted(() => {
  // 清理防抖函数
  window.removeEventListener('scroll', debounce(handleScroll, 100))
})

function debounce(fn, delay) {
  let timer = null
  return function(...args) {
    if (timer) clearTimeout(timer)
    timer = setTimeout(() => fn.apply(this, args), delay)
  }
}
</script>

六、常见问题和注意事项

6.1 生命周期执行顺序

javascript
// 父组件和子组件的生命周期顺序
父 beforeCreate → 父 created → 父 beforeMount
子 beforeCreate → 子 created → 子 beforeMount → 子 mounted
父 mounted

// 更新顺序
父 beforeUpdate → 子 beforeUpdate → 子 updated → 父 updated

// 卸载顺序
父 beforeUnmount → 子 beforeUnmount → 子 unmounted → 父 unmounted

6.2 异步操作注意事项

<script setup>
import { onMounted } from 'vue'

onMounted(async () => {
  // 正确:使用 await
  await fetchData()
  
  // 注意:如果不需要等待,可以直接调用
  fetchDataWithoutAwait()
  
  // 错误:不能直接使用 async 返回值
  // const result = await fetchData() // 这行没问题
  // 但 onMounted 本身返回 Promise 可能导致问题
})

// 更好的做法
onMounted(() => {
  loadData()
})

async function loadData() {
  // 异步逻辑
}
</script>

6.3 内存泄漏预防

<script setup>
import { onUnmounted } from 'vue'

// 需要清理的资源
let resources = []

onUnmounted(() => {
  // 清理所有资源
  resources.forEach(resource => {
    if (resource.dispose) resource.dispose()
    if (resource.abort) resource.abort()
    if (resource.removeEventListener) {
      // 移除事件监听
    }
  })
  resources = []
  
  // 清理全局事件
  window.removeEventListener('resize', handleResize)
  document.removeEventListener('click', handleClick)
})
</script>

七、总结

关键点:

  1. Composition API 钩子以 on 开头,在 setup() 中同步调用

  2. 挂载阶段 (onMounted):适合DOM操作和异步请求

  3. 更新阶段 (onUpdated):谨慎使用,避免无限循环

  4. 卸载阶段 (onUnmounted):必须清理资源,防止内存泄漏

  5. 错误处理 (onErrorCaptured):用于错误边界处理

  6. KeepAlive 组件:使用 onActivated/onDeactivated 管理状态

最佳实践:

  • 在正确的生命周期执行相应的操作

  • 及时清理资源,防止内存泄漏

  • 避免在 onUpdated 中修改响应式数据

  • 使用异步操作时注意错误处理

  • 考虑使用 onServerPrefetch 进行SSR优化

通过合理使用生命周期钩子,可以更好地控制组件行为,优化性能,并避免常见的问题。

 
posted @ 2026-02-10 16:02  麻辣~香锅  阅读(0)  评论(0)    收藏  举报