跳到主要内容

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 隐藏时组件仍然存在,因此计时器继续运行是预期行为。

参考:生命周期、模板引用。