Vue 3 中 onMounted 和 onUnmounted 的使用

FreeGuideOnline 最新 2026-07-07

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 中的 beforeDestroydestroyed

必须清理的场景:

  • 移除通过 addEventListener 添加的全局事件监听。
  • 清除定时器(clearIntervalclearTimeout)。
  • 取消订阅观察者(如 IntersectionObserverResizeObserver)。
  • 取消未完成的网络请求或清理流。
  • 销毁第三方 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 函数内,使得代码组织更加清晰。

注意事项与常见陷阱

  1. 同步注册
    onMountedonUnmounted 必须在 setup 的同步执行上下文中调用。如果将注册放在 setTimeoutPromise 中,钩子将无法与当前组件实例绑定,导致回调永远不会执行。

    // ❌ 错误示范:异步中注册
    setTimeout(() => {
      onMounted(() => { /* 永远不会触发 */ })
    }, 0)
    
  2. 多次调用会叠加执行
    与 Vue 2 的选项不同,你可以多次调用同一个钩子函数,注册多个回调。它们将按注册顺序依次执行。例如,可以将不同逻辑拆分为独立的组合函数,每个函数内部注册自己的 onMounted,而不必担心覆盖。

  3. 不要依赖子组件的挂载状态
    onMounted当前组件内部的所有子组件挂载之前执行。如果需要等待整个视图都渲染完毕,可以使用 nextTick

    import { nextTick, onMounted } from 'vue'
    
    onMounted(async () => {
      await nextTick()
      // 此时所有孩子组件也已更新完毕
    })