3.7 生命周期与 DOM 引用
本章目标: 在正确时机访问 DOM,及时释放组件创建的外部资源。
常用生命周期
| 阶段 | API | 常见用途 |
|---|---|---|
| 初始化逻辑 | <script setup> 顶层 | 声明状态、注册钩子、组合式函数 |
| 已挂载 | onMounted | 使用已存在的 DOM、启动交互资源 |
| 已更新 | onUpdated | 观察 DOM 更新,谨慎使用 |
| 即将卸载 | onBeforeUnmount | 在 DOM 移除前释放资源 |
| 已卸载 | onUnmounted | 停止定时器、事件监听 |
“挂载”表示组件已经进入页面,不等于所有 API 请求和所有子组件异步任务都完成。
DOM 引用与定时器
src/App.vue
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue';
const input = ref<HTMLInputElement | null>(null);
const seconds = ref(0);
let timer: ReturnType<typeof setInterval> | undefined;
onMounted(() => {
input.value?.focus();
timer = setInterval(() => seconds.value++, 1000);
});
onUnmounted(() => {
if (timer !== undefined) clearInterval(timer);
});
</script>
<template>
<main>
<label>任务名称 <input ref="input" /></label>
<p>当前组件已运行 {{ seconds }} 秒</p>
</main>
</template>
模板中的 ref="input" 会把 DOM 元素交给脚本里的引用。元素创建前以及移除后,引用可能为 null,因此要做空值判断。
Vue 3.5+ 还提供 useTemplateRef<HTMLInputElement>('input')。这里使用显式 ref,便于理解状态与 DOM 引用的共同基础。
注册与清理是成对的
浏览器事件监听片段
function onResize() {
console.log(window.innerWidth);
}
onMounted(() => window.addEventListener('resize', onResize));
onUnmounted(() => window.removeEventListener('resize', onResize));
移除时必须传入同一个函数引用。removeEventListener('resize', () => ...) 创建了新函数,不能移除旧监听器。
对照 IDisposable
定时器、浏览器事件、WebSocket 和第三方编辑器也有需要释放的资源。组件卸载可以作为资源释放边界,但 Vue 不会自动替你关闭所有外部对象。
请求在卸载时取消
请求生命周期片段
const controller = new AbortController();
onMounted(async () => {
try {
const response = await fetch('/api/tasks', { signal: controller.signal });
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data: unknown = await response.json();
if (!controller.signal.aborted) console.log(data);
} catch (error) {
if (!controller.signal.aborted) console.error(error);
}
});
onUnmounted(() => controller.abort());
实际页面还要把加载与失败状态显示出来,详见HTTP 章节。取消请求有助于避免资源浪费;保存动作在服务端可能已经执行,取消浏览器等待不等于回滚服务端事务。
常见误用
在 onUpdated 中无条件修改响应式状态可能造成更新循环。生命周期钩子应在 setup 的同步阶段注册;不要先 await 很久,再注册一个普通 onMounted。
如果组件被 KeepAlive 缓存,离开视图时可能只是停用,之后又激活。资源策略还需要考虑 onActivated / onDeactivated,不能只依赖卸载。
练习
把计时组件通过 v-if 放在父组件中,反复显示和隐藏。检查隐藏后计时器是否停止,重新显示后是否从零开始。
检查答案
每次 v-if 重新创建组件都会得到新的 seconds 状态。卸载后应清理原定时器。用 v-show 隐藏时组件仍然存在,因此计时器继续运行是预期行为。