12Vue3 生命周期钩子详解
一、生命周期概述
1.1 什么是生命周期
Vue组件从创建到销毁的整个过程,Vue在关键时刻提供钩子函数,允许我们在特定阶段执行自定义逻辑。
1.2 生命周期图示
创建阶段: 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 API | Options 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 生命周期执行顺序
// 父组件和子组件的生命周期顺序
父 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>
七、总结
关键点:
-
Composition API 钩子以
on开头,在setup()中同步调用 -
挂载阶段 (
onMounted):适合DOM操作和异步请求 -
更新阶段 (
onUpdated):谨慎使用,避免无限循环 -
卸载阶段 (
onUnmounted):必须清理资源,防止内存泄漏 -
错误处理 (
onErrorCaptured):用于错误边界处理 -
KeepAlive 组件:使用
onActivated/onDeactivated管理状态
最佳实践:
-
在正确的生命周期执行相应的操作
-
及时清理资源,防止内存泄漏
-
避免在
onUpdated中修改响应式数据 -
使用异步操作时注意错误处理
-
考虑使用
onServerPrefetch进行SSR优化
通过合理使用生命周期钩子,可以更好地控制组件行为,优化性能,并避免常见的问题。

浙公网安备 33010602011771号