第9次 · 生命周期钩子与模板引用
第9次 · 生命周期钩子与模板引用
第1/7页 · 先知道今天要做什么
前八课都在处理“数据怎么变成界面”“组件之间怎么说话”。但有一类事情和数据没关系,它们发生在特定时刻:页面第一次出现时要去后端拉数据、组件消失时要把定时器关掉、想直接操作某个 DOM 元素(让它获得焦点、滚动到某处)。
这些“特定时刻”就是生命周期。 组件从创建到销毁,会经过几个固定的时间点,Vue 允许你在这些时间点上挂一段自己的代码。今天要掌握的就是:在什么时刻该做什么事。
同时学一个配套技能:模板引用(template ref)——怎么在代码里拿到某个元素的真实 DOM。
今天的场景
一个“服务器监控小面板”。它要演示三件事:
- 组件第一次挂载时启动一个定时器,每秒刷新数据;
- 组件被销毁时把定时器清掉(不清掉就是内存泄漏,这是新手最容易犯的错);
- 用模板引用拿到输入框,页面一打开就自动聚焦,点按钮也能重新聚焦。
下课时拿出这三样结果
| 做到什么 | 在哪里看 | 完成的证据 |
|---|---|---|
会在 onMounted 里做初始化 |
面板数据 | 页面一打开,时间与 CPU 数值就自动出现并每秒刷新。 |
会在 onUnmounted 里清理副作用 |
Console 日志 | 卸载组件后,Console 不再打印任何东西(证明定时器被清掉了)。 |
| 会用模板引用操作 DOM | 输入框 | 页面加载后光标自动在输入框里;点按钮能再次聚焦。 |
你只需要带着这些基础来
会 ref、computed、watch、v-if、组件拆分;第 8 课的工程还能跑。
本课时 45 分钟怎么上
| 时间 | 老师带着做 | 学生留下什么 |
|---|---|---|
| 0—6 分钟 | 现场演示“不清理定时器”的后果:卸载组件后 Console 还在刷。 | 明白副作用必须成对出现。 |
| 6—20 分钟 | 讲六个钩子的执行顺序,重点讲 onMounted 与 onUnmounted。 |
笔记里写下六钩子的顺序。 |
| 20—30 分钟 | 讲模板引用:怎么声明、什么时候能拿到值、nextTick 解决什么。 |
能说出“为什么不能在 setup 顶层直接读 DOM”。 |
| 30—42 分钟 | 粘贴本课 App.vue,做挂载/卸载与聚焦实验。 |
三个实验全部跑通。 |
| 42—45 分钟 | 抽查:问“定时器为什么必须清”。 | 全员提交。 |
从哪里开始
复制第 8 课的工程,改名 lesson-vue-09。不需要新增依赖。
今天先掌握到这里
第 10 课是阶段一综合实战,会把第 1—9 课的内容全部串成一个学员管理系统。
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档(生命周期钩子 / 模板引用章节)· 组件依据 Element Plus 2.8 官方文档
第2/7页 · 把新词说清楚
新词与本课含义
| 名字 | 用熟悉的话理解 | 本课里长什么样 |
|---|---|---|
| 生命周期钩子 | 组件生命中的固定时间点,你可以挂一段代码上去。 | onMounted(() => ...) |
挂载 mounted |
组件的界面第一次真正出现在页面上。 | 拉数据、启动定时器、聚焦输入框 |
卸载 unmounted |
组件的界面被移除了,它的一切都要收尾。 | 清定时器、取消订阅、断开连接 |
| 副作用的成对原则 | 凡是“开启”了什么,就要在卸载时“关闭”什么。 | 定时器 / 事件监听 / WebSocket |
| 模板引用 | 在代码里拿到某个 DOM 元素或子组件的引用。 | const inputRef = ref(null) |
nextTick |
等这一轮界面更新完成之后,再执行下一句。 | 改完 v-if 后立刻读 DOM |
六个钩子与执行顺序
组件创建
↓
setup() ← 你写代码的地方(相当于早期)
↓ onBeforeMount ← 界面还没生成
↓ onMounted ← 界面已生成,能读 DOM 了 ★最常用
↓ 【数据变化】onBeforeUpdate ← 数据变了,界面还没重画
↓ 【数据变化】onUpdated ← 界面重画完
↓
onBeforeUnmount ← 组件即将销毁
↓ onUnmounted ← 已销毁,做清理 ★必须记住
把资源花在这两个上就够了:
| 钩子 | 什么时候用 | 典型代码 |
|---|---|---|
onMounted |
需要“界面存在之后”才能做的事 | 拉接口、启动定时器、初始化图表、聚焦输入框 |
onUnmounted |
需要“组件消失之后”必须收尾的事 | clearInterval、removeEventListener、关闭 WebSocket |
onUpdated 要格外小心:在它里面改数据,很容易造成“改了又更新、更新了又改”的死循环。多数情况下你想做的事应该用 watch 或 computed 完成。
为什么副作用必须成对
// ❌ 危险的写法:只开不关
onMounted(() => {
setInterval(() => { tick() }, 1000)
})
// ✅ 正确写法:开了就要关
let timer = null
onMounted(() => {
timer = setInterval(() => { tick() }, 1000)
})
onUnmounted(() => {
clearInterval(timer) // 不清就是内存泄漏 + 后台空转
})
为什么必须清? 定时器不属于组件,它是浏览器的。组件被卸载了,定时器还在跑,回调里还在改一个“已经不存在”的组件的数据,既浪费性能又容易报错。同理,window.addEventListener 不加 removeEventListener,每次挂载都多一个监听器,挂载十次就有十个。
模板引用:三步
<script setup>
import { ref, onMounted } from 'vue'
const inputRef = ref(null) // ① 起一个同名的 ref 变量,初始值 null
onMounted(() => {
inputRef.value.focus() // ③ 挂载后才能用(此时才真正是 DOM)
})
</script>
<template>
<input ref="inputRef" /> <!-- ② 在标签上写 ref="变量名" -->
</template>
三个必须知道的细节:
- 只能在
onMounted之后读。在setup顶层打印inputRef.value,得到的是null——因为那时界面还没生成。 ref="inputRef"里写的是字符串名字,必须和变量名一致。- 元素是
v-if控制的时候,被隐藏期间inputRef.value会变成null,用之前要判断。
nextTick 解决什么问题
数据一改,Vue 不会立刻重画界面(它要攒一批一起改,这样更快)。所以下面这段是错的:
show.value = true
inputRef.value.focus() // ❌ 此时输入框还没被创建,inputRef.value 是 null
正确写法:
import { nextTick } from 'vue'
show.value = true
await nextTick() // 等界面更新完成
inputRef.value.focus() // ✅ 现在能拿到了
一句话记法:改完数据想立刻碰 DOM,就 await nextTick()。
只看这几行:一份完整的“开与关”
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const now = ref('')
let timer = null
onMounted(() => {
console.log('组件挂载了')
timer = setInterval(() => { now.value = new Date().toLocaleTimeString() }, 1000)
})
onUnmounted(() => {
console.log('组件卸载了,清掉定时器')
clearInterval(timer)
})
</script>
先口头预测:如果只写 onMounted 不写 onUnmounted,把这个组件用 v-if 隐藏掉,Console 里的时间还会继续走吗?
会继续走。now 每秒钟还在更新,只是页面看不见它了。这就是“没清理”的代价——白耗性能。而且在真实项目里,这类残留还会导致内存占用越来越高。
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档(生命周期钩子 / 模板引用章节)· 组件依据 Element Plus 2.8 官方文档
第3/7页 · 先看一个小例子
本课使用的 App.vue(单文件,含一个带生命周期的内联组件)
<script setup>
import { ref, computed, onMounted, onUnmounted, nextTick, defineComponent, h } from 'vue'
import {
ElCard, ElButton, ElInput, ElTag, ElProgress, ElAlert, ElSwitch,
ElDescriptions, ElDescriptionsItem, ElMessage
} from 'element-plus'
import 'element-plus/dist/index.css'
/* ============ 内联子组件:Monitor(演示完整的生命周期) ============ */
const Monitor = defineComponent({
name: 'Monitor',
emits: ['log'],
setup(props, { emit }) {
const now = ref('')
const cpu = ref(20)
const mem = ref(45)
const ticks = ref(0)
let timer = null
function tick() {
ticks.value++
now.value = new Date().toLocaleTimeString()
// 造一些看起来在波动的数据
cpu.value = Math.min(98, Math.max(5, cpu.value + Math.round((Math.random() - 0.5) * 24)))
mem.value = Math.min(95, Math.max(10, mem.value + Math.round((Math.random() - 0.5) * 10)))
}
onMounted(() => {
emit('log', 'Monitor 挂载完成 → 启动定时器')
tick()
timer = setInterval(tick, 1000)
})
// ⚠️ 关键:组件被销毁时必须清掉定时器,否则它会在后台一直跑
onUnmounted(() => {
emit('log', 'Monitor 已卸载 → 清除定时器(Console 不再打印)')
clearInterval(timer)
})
function statusType(v) {
if (v >= 85) return 'danger'
if (v >= 60) return 'warning'
return 'success'
}
return () => h('div', null, [
h(ElDescriptions, { column: 2, border: true }, {
default: () => [
h(ElDescriptionsItem, { label: '当前时间' }, () => now.value || '—'),
h(ElDescriptionsItem, { label: '已刷新次数' }, () => String(ticks.value)),
h(ElDescriptionsItem, { label: 'CPU 使用率' }, () => [
h(ElTag, { type: statusType(cpu.value), size: 'small' }, () => cpu.value + '%')
]),
h(ElDescriptionsItem, { label: '内存使用率' }, () => [
h(ElTag, { type: statusType(mem.value), size: 'small' }, () => mem.value + '%')
])
]
}),
h('div', { class: 'bars' }, [
h(ElProgress, { percentage: cpu.value, strokeWidth: 14,
color: cpu.value >= 85 ? '#f56c6c' : cpu.value >= 60 ? '#e6a23c' : '#67c23a' }),
h(ElProgress, { percentage: mem.value, strokeWidth: 14 })
])
])
}
})
/* ============ 父组件 ============ */
const showMonitor = ref(true)
const logs = ref([])
const inputRef = ref(null)
const draft = ref('')
const showSearchBox = ref(false)
const searchRef = ref(null)
const lastLog = computed(() => logs.value[logs.value.length - 1] || '(还没有日志)')
function addLog(text) {
const t = new Date().toLocaleTimeString()
logs.value.push(`[${t}] ${text}`)
console.log(text)
if (logs.value.length > 12) logs.value.shift()
}
// ① 页面加载完成后自动聚焦输入框
onMounted(() => {
addLog('父组件挂载完成 → 自动聚焦输入框')
inputRef.value && inputRef.value.focus()
})
// ② 手动聚焦
function focusInput() {
inputRef.value && inputRef.value.focus()
ElMessage.success('已聚焦到输入框')
}
// ③ nextTick 演示:先显示搜索框,再聚焦(不 await 就会失败)
async function openSearch() {
showSearchBox.value = true
// 试一试把下面这行注释掉,会出现 Cannot read properties of null
await nextTick()
searchRef.value && searchRef.value.focus()
addLog('搜索框已显示并聚焦(nextTick 之后才拿到 DOM)')
}
onUnmounted(() => {
console.log('父组件卸载')
})
</script>
<template>
<div class="page">
<h1 class="title">生命周期钩子与模板引用</h1>
<el-card class="block">
<template #header>
<div class="hd">
<span>服务器监控(内联子组件 Monitor)</span>
<el-switch v-model="showMonitor" active-text="挂载 / 卸载" />
</div>
</template>
<!-- v-if 为 false 时组件被卸载,onUnmounted 被触发 -->
<Monitor v-if="showMonitor" @log="addLog" />
<p v-else class="gone">组件已卸载。观察 Console:不再有任何时间打印。</p>
</el-card>
<el-card class="block">
<template #header>模板引用:自动聚焦</template>
<el-input ref="inputRef" v-model="draft" placeholder="页面加载后光标应该已经在这里" clearable />
<div class="ops">
<el-button type="primary" @click="focusInput">重新聚焦</el-button>
<el-button @click="openSearch">显示搜索框并聚焦(演示 nextTick)</el-button>
</div>
<el-input
v-if="showSearchBox"
ref="searchRef"
placeholder="这个框是刚被 v-if 创建出来的"
clearable
/>
</el-card>
<el-card class="block">
<template #header>生命周期日志(最新一条:{{ lastLog }})</template>
<pre class="log">{{ logs.join('\n') || '暂无' }}</pre>
<el-alert
title="做实验:把右上角开关关掉,Console 应该停止打印时间与 CPU 数据——这证明 onUnmounted 里的 clearInterval 生效了。"
type="info" :closable="false" show-icon
/>
</el-card>
</div>
</template>
<style>
html, body, #app {
margin: 0; padding: 0; max-width: none; width: auto; height: auto;
display: block; place-items: initial; text-align: left; background: #f5f7fa;
}
</style>
<style scoped>
.page { max-width: 900px; margin: 28px auto; padding: 0 16px; }
.title { font-size: 22px; color: #303133; margin: 0 0 18px; }
.block { margin-bottom: 16px; }
.hd { display: flex; justify-content: space-between; align-items: center; }
.bars { margin-top: 12px; }
.gone { color: #909399; margin: 4px 0; }
.ops { margin: 12px 0; display: flex; gap: 10px; flex-wrap: wrap; }
.log {
background: #1f2430; color: #a9dc76; padding: 12px; border-radius: 6px;
font-size: 12px; line-height: 1.7; margin: 0 0 12px; max-height: 220px; overflow: auto;
}
</style>
预期看见什么
- 第一张卡片:右上角一个“挂载/卸载”开关,下面显示当前时间、已刷新次数、CPU 与内存使用率,还有两条彩色进度条。这些东西每秒自己动一次。
- 第二张卡片:一个输入框,光标已经在里面(页面一打开就聚焦了)。下面两个按钮。
- 点“重新聚焦”,光标回到输入框。
- 点“显示搜索框并聚焦”,下面出现一个新输入框且光标已经在里面。
- 第三张卡片:一串带时间的日志。把第一张卡片的开关关掉,日志里出现“Monitor 已卸载 → 清除定时器”,Console 里的时间打印停止。
先猜一猜,读完代码再回答
为什么 openSearch 里必须写 await nextTick()?
async function openSearch() {
showSearchBox.value = true
await nextTick()
searchRef.value && searchRef.value.focus()
}
因为 showSearchBox 改成 true 只是“改了数据”,Vue 不会立刻把输入框画到页面上。 如果不等,searchRef.value 还是 null,focus() 就会报错。nextTick 的意义就是“等这一次界面更新做完,再往下走”。可以自己把这个 await 注释掉试试,就能看到报错。
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档(生命周期钩子 / 模板引用章节)· 组件依据 Element Plus 2.8 官方文档
第4/7页 · 一步一步做出来
第1步:亲眼看到六个钩子的顺序
<script setup>
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, ref } from 'vue'
const n = ref(0)
onBeforeMount(() => console.log('1 onBeforeMount'))
onMounted(() => console.log('2 onMounted'))
onBeforeUpdate(() => console.log('3 onBeforeUpdate'))
onUpdated(() => console.log('4 onUpdated'))
onBeforeUnmount(() => console.log('5 onBeforeUnmount'))
onUnmounted(() => console.log('6 onUnmounted'))
</script>
<template>
<p>{{ n }}</p>
<button @click="n++">改数据</button>
</template>
刷新页面,预期:Console 依次打印 1、2。点“改数据”,预期:打印 3、4。把整个组件用 v-if 隐藏,预期:打印 5、6。
第2步:制造一次内存泄漏
把第 3 页 Monitor 组件里的 onUnmounted 整段删掉。刷新页面后点开关卸载组件。预期:Console 里的时间与 CPU 打印继续出现,而且每次重新挂载都会多一个定时器,刷新速度越来越快(1 秒、0.5 秒、0.33 秒……)。看完把 onUnmounted 加回来。
第3步:体验 setup 阶段读不到 DOM
在 <script setup> 顶层加:
const inputRef = ref(null)
console.log('setup 阶段的 inputRef:', inputRef.value) // 预期:null
再把同样的打印放到 onMounted 里,预期:这次拿到真实元素。
第4步:模板引用的三种情况
<!-- ① 普通元素 -->
<input ref="a" />
<!-- ② v-for 里:拿到的是数组 -->
<div v-for="i in 3" :key="i" ref="listRefs">{{ i }}</div>
<!-- ③ v-if 为 false 时:ref 的值是 null -->
<div v-if="show" ref="b">内容</div>
在 onMounted 里分别打印 a.value、listRefs.value.length、b.value,看清楚三种结果。
第5步:nextTick 取反实验
把 openSearch 里的 await nextTick() 注释掉,点按钮,预期:Console 报 Cannot read properties of null (reading 'focus')。加回来,预期:正常聚焦。
第6步:整段替换为本课完整版
把第 3 页的完整 App.vue 覆盖 src/App.vue,把三张卡片全部跑通。
本课文件速查
| 位置 | 本次作用 |
|---|---|
Monitor 的 onMounted |
启动定时器、立刻刷新一次数据。 |
Monitor 的 onUnmounted |
清掉定时器,避免后台空转。 |
父组件 onMounted |
页面加载后自动聚焦输入框。 |
inputRef / searchRef |
两个模板引用,分别指向两个输入框。 |
await nextTick() |
等界面更新完成后才去操作新创建的 DOM。 |
完成修改后检查
| 操作或输入 | 预期观察 |
|---|---|
| 刷新页面 | 时间、CPU、内存立刻有值,并每秒变化。 |
| 关掉开关 | 日志出现“已卸载”,Console 停止打印时间。 |
| 再打开开关 | 定时器重新启动(只启动一个,刷新速度没有变快)。 |
| 点“重新聚焦” | 光标回到输入框。 |
| 点“显示搜索框并聚焦” | 新输入框出现且获得焦点。 |
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档(生命周期钩子 / 模板引用章节)· 组件依据 Element Plus 2.8 官方文档
第5/7页 · 换成自己的代码
你的任务
- 换成你自己的定时任务:把
Monitor改成你关心的一组数据(例如“今日待办剩余数”“课程完成百分比”),仍然每秒或每三秒刷新一次。 - 加一条事件监听:在
onMounted里加window.addEventListener('resize', onResize),记录窗口宽度变化次数并在页面上显示;在onUnmounted里removeEventListener把它去掉。 - 加一个“手动销毁”按钮:不用
v-if,而是通过一个showMonitor标志切换(这就是当前的做法),但要求在日志里能清楚看到“挂载 → 卸载 → 再挂载”的完整过程。 - 给第二个输入框加一个自动清空:点击“显示搜索框并聚焦”时,如果搜索框已经存在,则把它清空再聚焦(提示:可以直接用
searchRef.value.clear(),因为 Element Plus 的el-input暴露了clear方法)。 - 说清一件小事:把
onUpdated加进Monitor,在里面console.log('更新了'),观察一秒打印多少次,并写下为什么不要在这个钩子里改数据。
按这张操作单完成
| 步骤 | 改哪里 | 运行后应该看到什么 |
|---|---|---|
| 1 换数据 | Monitor 的 ref 与 render |
页面显示你自己的指标。 |
| 2 加监听 | onMounted / onUnmounted |
改变窗口大小时计数增加。 |
| 3 完整日志 | addLog 调用点 |
日志能读出“挂了 → 卸了 → 又挂了”。 |
| 4 搜索框清空 | openSearch |
已有内容时先清空再聚焦。 |
| 5 观察 onUpdated | Monitor 加钩子 |
Console 每秒打印一次,明白它的触发频率。 |
最低完成标准:先把这三项做实
| 检查项 | 通过条件 |
|---|---|
| 成对原则 | 代码里凡是 setInterval / addEventListener,都对应一处 clearInterval / removeEventListener。 |
| 钩子选择正确 | 初始化逻辑在 onMounted,清理逻辑在 onUnmounted,没有把请求写在 setup 顶层。 |
| 模板引用可用 | 至少两处 ref="xxx" 能在代码里拿到并操作(focus / clear)。 |
在本地写练习记录
- 抄下第 1 步六个钩子的实际打印顺序。
- 记录“制造内存泄漏”实验的现象:删掉
onUnmounted后 Console 的打印频率怎么变的。 - 写下
nextTick取反实验的报错原文与结论。
最后交什么
提交 src/App.vue 与 练习记录.md。
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档(生命周期钩子 / 模板引用章节)· 组件依据 Element Plus 2.8 官方文档
第6/7页 · 出错时从哪里查
把现象和检查动作对上
| 看到什么现象 / 报错 | 先检查哪里 | 修好后怎样确认 |
|---|---|---|
Cannot read properties of null (reading 'focus') |
在 DOM 还没生成时就操作了它(setup 顶层,或 v-if 刚改完没等 nextTick)。 |
移到 onMounted 里,或在改完数据后 await nextTick()。 |
| 组件隐藏后 Console 还在打印 | 定时器没清,或 onUnmounted 里清的不是同一个变量。 |
在 onUnmounted 里确认清的就是 onMounted 里赋值的那个变量。 |
| 每次挂载后刷新速度变快 | 定时器被重复创建且旧的没清掉。 | 清理后速度正常。 |
onUpdated 里改了数据导致页面卡死/刷屏 |
在 onUpdated 里改数据。 |
换成 watch 或 computed。 |
ref="xxx" 拿到的永远是 null |
变量名与 ref 属性值不一致;或元素在 v-if 里且条件是 false。 |
名字一致且元素可见后能拿到。 |
在 onMounted 里发请求后马上 console.log 为空 |
请求是异步的,此时还没回来。 | 把打印放进 .then() 或 async/await 之后。 |
| 卸载后仍报“组件不存在”相关错误 | 异步回调在组件销毁后还在执行。 | 清理时取消请求(AbortController)或加“是否已卸载”标志。 |
排错口诀
要碰 DOM 等挂载,挂载之前拿不到; 改了数据想碰 DOM,先 await nextTick; 凡有 interval / addEventListener,必有对应的清理; onUpdated 里别改数据,那是死循环的入口。
不要用这些办法“修复”
- 不要为了“让定时器不报错”而把
setInterval写到setup顶层——那样每次组件重新创建都会再开一个,越跑越多。 - 不要在
onUnmounted里把数据重置一遍当作“清理”。真正要做的是解除对外部资源(定时器、事件、连接)的占用。 - 不要用
setTimeout猜时间(例如setTimeout(fn, 300))来等 DOM 出现,nextTick才是正确做法。
需要老师帮忙时交这些
出问题那段代码(钩子名 + 里面写了什么)、Console 报错原文、你做了什么操作、期望发生什么、实际发生什么。
老师巡视时只问三句话
- 你的初始化代码放在哪个钩子里?为什么不能放在 setup 顶层?(能答出此时还没有 DOM / 组件未挂载)
- 你开了哪些需要清理的东西?清理代码在哪?(能一一对应)
- 为什么这里必须
nextTick?(能说出“数据改了但界面还没更新”)
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档(生命周期钩子 / 模板引用章节)· 组件依据 Element Plus 2.8 官方文档
第7/7页 · 用自己的话讲一遍
回到第 3 页:你刚才猜对了吗?
为什么 openSearch 必须等 nextTick?
参考解释:Vue 更新界面是“攒一批一起改”的。改 showSearchBox 只是改了数据,DOM 还没创建。nextTick 返回一个 Promise,等当前这一轮界面更新完成后才继续执行,此时 searchRef.value 才指向真实的输入框实例。
回答本课的两个问题
| 问题 | 参考解释 |
|---|---|
| 为什么副作用必须成对出现? | 因为定时器、事件监听、连接都属于浏览器/操作系统的资源,不属于组件。组件销毁时它们不会自动消失,会继续执行回调、占用内存。必须在 onUnmounted 里手动解除。 |
为什么不能在 onUpdated 里改数据? |
onUpdated 在每次界面更新后触发,在里面改数据会再触发一次更新,于是无限循环。 |
五个必须能答上来的问题
| 考考自己 | 可以这样通俗回答 |
|---|---|
| 拉接口数据应该放在哪个钩子? | onMounted(越早拿到越好,但要在界面存在之后)。 |
| 模板引用声明后,什么时候才有值? | onMounted 之后。v-if 为 false 时又会变成 null。 |
nextTick 什么时候用? |
改完数据马上要操作 DOM 的时候。 |
| 组件销毁时一定要清什么? | 定时器、事件监听、WebSocket、正在进行的请求。 |
onBeforeUnmount 和 onUnmounted 的区别? |
前者在销毁之前(DOM 还在),后者在销毁之后。清理通常写在 onUnmounted。 |
一分钟现场演示指南
- 展示自动刷新:指着时间和进度条说“这些都是
onMounted里启动的定时器在干活”。 - 展示清理:关掉开关,Console 停止打印,说“这一行
clearInterval就是合格与不合格的分界线”。 - 展示模板引用:刷新页面,光标已在输入框里;点按钮再次聚焦。
- 收尾一句:“该在什么时候做事,就写在对应的钩子里;开了什么,就要记得关掉什么。”
交作业前最后检查
src/App.vue与练习记录.md已保存。- 每个
setInterval/addEventListener都有对应清理。 - 模板引用至少两处可用。
nextTick取反实验的现象已记录。- Console 无红色报错。
课后再做一个小对照
把 Monitor 里“每秒自动刷新”改成“点按钮手动刷新一次”(去掉定时器)。写下你更倾向哪种方式、在真实项目里哪种更常见,以及理由(提示:轮询会带来额外请求量,实时性要求高时才值得每秒拉一次)。
来源:本课自编 Vue 3 前端核心技能实训 · 语法依据 Vue 3.5 官方文档(生命周期钩子 / 模板引用章节)· 组件依据 Element Plus 2.8 官方文档