Vue 3 中 onMounted 和 onUnmounted 的使用
js import { onMounted, onUnmounted } from 'vue'
这些钩子函数与组件的实例绑定,因此只能在 `setup` 执行期间同步调用——也就是在组件的生命周期内注册。不能在异步回调中注册,否则会丢失绑定关系。
### onMounted:挂载后执行
`onMounted` 注册的回调会在组件被挂载到 DOM 之后立即执行。此时,组件的模板已被渲染,你可以安全地访问 DOM 节点、初始化第三方库、启动网络请求或设置定时器。它是替代 Vue 2 中 `mounted` 选项的直接方式。
**常见用途:**
- 获取 DOM 元素引用并初始化图表、地图等。
- 添加全局事件监听(`window.addEventListener`)。
- 启动 `setInterval` / `setTimeout` 定时器。
- 从服务器获取初始数据。
- 注册第三方插件。
```vue
<template>
<div ref="chartContainer"></div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const chartContainer = ref(null)
onMounted(() => {
// 此时 chartContainer.value 指向了真实 DOM 元素
const ctx = chartContainer.value.getContext('2d')
// 在此初始化你的图表库
console.log('组件已挂载,DOM 可访问')
})
</script>
注意: 如果你需要在
onMounted中操作的数据依赖于响应式状态,那么该状态必须已经初始化。因为onMounted执行时,setup中的响应式数据已经全部准备好。
onUnmounted:卸载前清理
onUnmounted 注册的回调会在组件卸载(销毁)之前调用。这为你提供了执行清理任务的最后机会,防止内存泄漏和意外行为。它相当于 Vue 2 中的 beforeDestroy 或 destroyed。
必须清理的场景:
- 移除通过
addEventListener添加的全局事件监听。 - 清除定时器(
clearInterval、clearTimeout)。 - 取消订阅观察者(如
IntersectionObserver、ResizeObserver)。 - 取消未完成的网络请求或清理流。
- 销毁第三方 UI 组件的实例(如弹出框、模态框)。
<script setup>
import { onMounted, onUnmounted } from 'vue'
let timer = null
onMounted(() => {
// 每秒更新一次数据
timer = setInterval(() => {
console.log('定时器执行中...')
}, 1000)
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
// 清除定时器
if (timer) {
clearInterval(timer)
timer = null
}
// 移除事件监听
window.removeEventListener('resize', handleResize)
console.log('组件已卸载,定时器和事件监听被清除')
})
function handleResize() {
// 处理窗口大小变化
}
</script>
没有在
onUnmounted中清理的定时器或事件监听会在组件销毁后继续存在,导致不可控的错误或内存堆积。
一个完整的实践示例:自动保存计数器
以下组件演示了如何在挂载时从 localStorage 读取数据,并在卸载时自动保存当前状态。这是一个经典的 onMounted + onUnmounted 配合场景。
<template>
<div>
<p>计数:{{ count }}</p>
<button @click="count++">+1</button>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const STORAGE_KEY = 'my-app-count'
const count = ref(0)
onMounted(() => {
// 组件挂载后读取本地存储中的历史值
const saved = localStorage.getItem(STORAGE_KEY)
if (saved !== null) {
count.value = Number(saved)
}
})
onUnmounted(() => {
// 组件卸载前将当前值保存到本地存储
localStorage.setItem(STORAGE_KEY, count.value.toString())
})
</script>
这个例子清晰地展示了“初始化”与“清理”对数据持久化的实际意义。即使组件被销毁再重新创建,也能恢复之前的状态。
与 Vue 2 写法的对比
| Vue 2 选项式 API | Vue 3 Composition API |
|---|---|
mounted() |
onMounted() |
beforeDestroy() |
onBeforeUnmount() |
destroyed() |
onUnmounted() |
在 Vue 2 中,你必须在选项对象中定义这些方法;而 Vue 3 允许你将相关的生命周期逻辑与数据放在同一个 setup 函数内,使得代码组织更加清晰。
注意事项与常见陷阱
-
同步注册
onMounted和onUnmounted必须在setup的同步执行上下文中调用。如果将注册放在setTimeout或Promise中,钩子将无法与当前组件实例绑定,导致回调永远不会执行。// ❌ 错误示范:异步中注册 setTimeout(() => { onMounted(() => { /* 永远不会触发 */ }) }, 0) -
多次调用会叠加执行
与 Vue 2 的选项不同,你可以多次调用同一个钩子函数,注册多个回调。它们将按注册顺序依次执行。例如,可以将不同逻辑拆分为独立的组合函数,每个函数内部注册自己的onMounted,而不必担心覆盖。 -
不要依赖子组件的挂载状态
onMounted在当前组件内部的所有子组件挂载之前执行。如果需要等待整个视图都渲染完毕,可以使用nextTick。import { nextTick, onMounted } from 'vue' onMounted(async () => { await nextTick() // 此时所有孩子组件也已更新完毕 })