生命周期钩子 #
一、生命周期概览 #
1.1 生命周期图示 #
text
创建阶段
├── beforeCreate (选项式API)
├── created (选项式API)
├── setup (组合式API)
│
挂载阶段
├── onBeforeMount
├── onMounted
│
更新阶段
├── onBeforeUpdate
├── onUpdated
│
卸载阶段
├── onBeforeUnmount
├── onUnmounted
│
特殊钩子
├── onErrorCaptured
├── onRenderTracked
├── onRenderTriggered
└── onActivated / onDeactivated (keep-alive)
1.2 API对照表 #
| 选项式API | 组合式API | 说明 |
|---|---|---|
| beforeCreate | setup() | 实例初始化前 |
| created | setup() | 实例创建后 |
| beforeMount | onBeforeMount | 挂载前 |
| mounted | onMounted | 挂载后 |
| beforeUpdate | onBeforeUpdate | 更新前 |
| updated | onUpdated | 更新后 |
| beforeUnmount | onBeforeUnmount | 卸载前 |
| unmounted | onUnmounted | 卸载后 |
| errorCaptured | onErrorCaptured | 捕获错误 |
二、创建阶段 #
2.1 setup替代beforeCreate和created #
vue
<script setup>
import { ref } from 'vue'
// setup在beforeCreate和created之间执行
// 此时组件实例已创建,但DOM还未挂载
const count = ref(0)
// 相当于created中的代码
console.log('组件已创建')
</script>
2.2 初始化操作 #
vue
<script setup>
import { ref, computed } from 'vue'
// 初始化响应式数据
const count = ref(0)
const name = ref('张三')
// 初始化计算属性
const doubled = computed(() => count.value * 2)
// 初始化方法
function increment() {
count.value++
}
// 调用初始化函数
initializeApp()
async function initializeApp() {
// 加载初始数据
const data = await fetchInitialData()
// 处理数据
}
</script>
三、挂载阶段 #
3.1 onBeforeMount #
vue
<script setup>
import { onBeforeMount, ref } from 'vue'
const element = ref(null)
onBeforeMount(() => {
// DOM还未挂载
console.log('挂载前')
console.log(element.value) // null
})
</script>
3.2 onMounted #
vue
<template>
<div ref="container">
<canvas ref="canvas"></canvas>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const container = ref(null)
const canvas = ref(null)
onMounted(() => {
// DOM已挂载,可以访问DOM元素
console.log(container.value)
console.log(canvas.value)
// 初始化第三方库
initChart()
// 添加事件监听
window.addEventListener('resize', handleResize)
})
function initChart() {
const ctx = canvas.value.getContext('2d')
// 绑制图表
}
function handleResize() {
console.log('窗口大小变化')
}
</script>
3.3 常见使用场景 #
vue
<script setup>
import { ref, onMounted } from 'vue'
// 1. 获取DOM元素
const inputRef = ref(null)
onMounted(() => {
inputRef.value.focus()
})
// 2. 发送网络请求
const data = ref(null)
const loading = ref(false)
onMounted(async () => {
loading.value = true
try {
const response = await fetch('/api/data')
data.value = await response.json()
} finally {
loading.value = false
}
})
// 3. 初始化第三方库
onMounted(() => {
// 初始化图表库
const chart = new Chart(ctx, config)
// 初始化地图
const map = new Map.Map('container', options)
})
// 4. 添加事件监听
onMounted(() => {
window.addEventListener('scroll', handleScroll)
document.addEventListener('keydown', handleKeydown)
})
</script>
四、更新阶段 #
4.1 onBeforeUpdate #
vue
<script setup>
import { ref, onBeforeUpdate } from 'vue'
const list = ref([1, 2, 3])
onBeforeUpdate(() => {
// DOM更新前
console.log('更新前,DOM还未重新渲染')
})
</script>
4.2 onUpdated #
vue
<script setup>
import { ref, onUpdated } from 'vue'
const count = ref(0)
onUpdated(() => {
// DOM更新后
console.log('DOM已更新')
// 注意:不要在这里修改响应式数据,可能导致无限循环
// count.value++ // 危险!
})
</script>
4.3 使用场景 #
vue
<template>
<div ref="scrollContainer">
<div v-for="item in messages" :key="item.id">
{{ item.text }}
</div>
</div>
</template>
<script setup>
import { ref, onUpdated, nextTick } from 'vue'
const messages = ref([])
const scrollContainer = ref(null)
onUpdated(() => {
// 新消息后自动滚动到底部
scrollToBottom()
})
function scrollToBottom() {
nextTick(() => {
scrollContainer.value.scrollTop = scrollContainer.value.scrollHeight
})
}
function addMessage(text) {
messages.value.push({
id: Date.now(),
text
})
}
</script>
五、卸载阶段 #
5.1 onBeforeUnmount #
vue
<script setup>
import { onBeforeUnmount } from 'vue'
onBeforeUnmount(() => {
// 组件卸载前
console.log('即将卸载')
// 可以在这里做一些确认操作
if (hasUnsavedChanges.value) {
// 提示用户保存
}
})
</script>
5.2 onUnmounted #
vue
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const timer = ref(null)
onMounted(() => {
// 设置定时器
timer.value = setInterval(() => {
console.log('定时器执行')
}, 1000)
// 添加事件监听
window.addEventListener('resize', handleResize)
document.addEventListener('click', handleClick)
})
onUnmounted(() => {
// 清理定时器
clearInterval(timer.value)
// 移除事件监听
window.removeEventListener('resize', handleResize)
document.removeEventListener('click', handleClick)
// 取消网络请求
controller.abort()
// 销毁第三方实例
chart.destroy()
map.destroy()
})
function handleResize() {}
function handleClick() {}
</script>
5.3 清理资源示例 #
vue
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
// WebSocket连接
let ws = null
onMounted(() => {
ws = new WebSocket('wss://example.com/socket')
ws.onmessage = (event) => {
console.log('收到消息:', event.data)
}
})
onUnmounted(() => {
if (ws) {
ws.close()
}
})
// 媒体流
const videoRef = ref(null)
let stream = null
onMounted(async () => {
stream = await navigator.mediaDevices.getUserMedia({ video: true })
videoRef.value.srcObject = stream
})
onUnmounted(() => {
if (stream) {
stream.getTracks().forEach(track => track.stop())
}
})
</script>
六、keep-alive相关钩子 #
6.1 onActivated #
vue
<script setup>
import { onActivated } from 'vue'
onActivated(() => {
// 组件被激活时(从缓存中恢复)
console.log('组件激活')
// 刷新数据
refreshData()
// 恢复滚动位置
restoreScrollPosition()
})
</script>
6.2 onDeactivated #
vue
<script setup>
import { onDeactivated } from 'vue'
onDeactivated(() => {
// 组件被停用时(进入缓存)
console.log('组件停用')
// 保存滚动位置
saveScrollPosition()
// 暂停视频
pauseVideo()
})
</script>
6.3 完整示例 #
vue
<template>
<div class="page" ref="pageRef">
<h2>{{ title }}</h2>
<div v-for="item in list" :key="item.id">
{{ item.name }}
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onActivated, onDeactivated } from 'vue'
const pageRef = ref(null)
const title = ref('列表页')
const list = ref([])
const scrollPosition = ref(0)
onMounted(async () => {
await loadList()
})
onActivated(() => {
// 恢复滚动位置
pageRef.value.scrollTop = scrollPosition.value
// 刷新数据(可选)
// loadList()
})
onDeactivated(() => {
// 保存滚动位置
scrollPosition.value = pageRef.value.scrollTop
})
async function loadList() {
const response = await fetch('/api/list')
list.value = await response.json()
}
</script>
七、错误处理钩子 #
7.1 onErrorCaptured #
vue
<script setup>
import { ref, onErrorCaptured } from 'vue'
const error = ref(null)
onErrorCaptured((err, instance, info) => {
// 捕获后代组件的错误
console.error('捕获到错误:', err)
console.error('组件实例:', instance)
console.error('错误信息:', info)
error.value = err.message
// 返回false阻止错误继续传播
return false
})
</script>
7.2 全局错误处理 #
javascript
// main.js
const app = createApp(App)
app.config.errorHandler = (err, instance, info) => {
console.error('全局错误:', err)
// 上报错误
reportError(err)
}
app.mount('#app')
八、调试钩子 #
8.1 onRenderTracked #
vue
<script setup>
import { onRenderTracked } from 'vue'
onRenderTracked((e) => {
// 组件渲染时追踪的响应式依赖
console.log('追踪依赖:', e)
})
</script>
8.2 onRenderTriggered #
vue
<script setup>
import { onRenderTriggered } from 'vue'
onRenderTriggered((e) => {
// 触发组件重新渲染的依赖变化
console.log('触发渲染:', e)
})
</script>
九、生命周期最佳实践 #
9.1 常见模式 #
vue
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
// 模式1:事件监听
onMounted(() => {
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
// 模式2:定时器
let timer = null
onMounted(() => {
timer = setInterval(pollData, 5000)
})
onUnmounted(() => {
clearInterval(timer)
})
// 模式3:网络请求
const controller = new AbortController()
onMounted(async () => {
try {
const response = await fetch('/api/data', {
signal: controller.signal
})
// 处理数据
} catch (e) {
if (e.name !== 'AbortError') {
console.error(e)
}
}
})
onUnmounted(() => {
controller.abort()
})
</script>
9.2 使用组合式函数封装 #
javascript
// useEventListener.js
import { onMounted, onUnmounted } from 'vue'
export function useEventListener(target, event, callback) {
onMounted(() => {
target.addEventListener(event, callback)
})
onUnmounted(() => {
target.removeEventListener(event, callback)
})
}
// 使用
import { useEventListener } from './useEventListener'
useEventListener(window, 'resize', handleResize)
useEventListener(document, 'click', handleClick)
javascript
// useInterval.js
import { ref, onUnmounted } from 'vue'
export function useInterval(callback, delay) {
const timer = ref(null)
function start() {
stop()
timer.value = setInterval(callback, delay)
}
function stop() {
if (timer.value) {
clearInterval(timer.value)
timer.value = null
}
}
onUnmounted(stop)
return { start, stop }
}
十、总结 #
钩子使用场景 #
| 钩子 | 使用场景 |
|---|---|
| setup | 初始化数据、计算属性 |
| onMounted | DOM操作、网络请求、事件监听 |
| onUpdated | DOM更新后操作 |
| onUnmounted | 清理定时器、事件监听、网络请求 |
| onActivated | keep-alive激活时恢复状态 |
| onDeactivated | keep-alive停用时保存状态 |
| onErrorCaptured | 捕获错误 |
最佳实践:
- 在onMounted中进行DOM操作
- 在onUnmounted中清理资源
- 避免在onUpdated中修改响应式数据
- 使用组合式函数封装重复逻辑
- keep-alive组件使用onActivated/onDeactivated
最后更新:2026-03-26