第 7 节:生命周期钩子
📚 本节目标
- 理解什么是生命周期
- 掌握常用的生命周期钩子(created、mounted)
- 学会在合适的时机执行代码
- 理解生命周期的执行顺序
一、什么是生命周期?
生活中的例子
一个人的生命周期:
- 🐣 出生 → 👶 婴儿 → 🧒 儿童 → 👨 成年 → 👴 老年 → ⚰️ 离世
每个阶段都可以做不同的事。
Vue 组件的生命周期
大白话:组件从创建到销毁的整个过程,就是生命周期。
Vue 组件的生命周期:
创建前 → 创建完成 → 挂载前 → 挂载完成 → 更新 → 销毁
生命周期钩子:在生命周期的特定时刻自动执行的函数。
类比:
- 生命周期 = 一天的时间线
- 钩子函数 = 闹钟(在特定时刻响起)
created= 早上 7 点的闹钟mounted= 早上 8 点的闹钟
二、生命周期图解(简化版)
┌─────────────────┐
│ 创建阶段 │
├─────────────────┤
│ beforeCreate │ ← 实例刚创建,data 还没初始化
│ ↓ │
│ created ★ │ ← data 已初始化,可以访问数据了
└─────────────────┘
↓
┌─────────────────┐
│ 挂载阶段 │
├─────────────────┤
│ beforeMount │ ← 开始渲染,但还没挂载到页面
│ ↓ │
│ mounted ★ │ ← 已经挂载到页面,可以操作 DOM 了
└─────────────────┘
↓
┌─────────────────┐
│ 更新阶段 │ (数据变化时触发)
├─────────────────┤
│ beforeUpdate │
│ ↓ │
│ updated │
└─────────────────┘
↓
┌─────────────────┐
│ 销毁阶段 │
├─────────────────┤
│ beforeUnmount │
│ ↓ │
│ unmounted │
└─────────────────┘
新手重点关注:
- ⭐
created:数据准备好了,但页面还没显示 - ⭐
mounted:页面已经显示了,可以操作 DOM
三、created 钩子
什么时候用 created?
场景:需要在页面显示之前准备数据。
常见用途:
- 请求后端数据
- 初始化一些变量
- 不涉及 DOM 操作的准备工作
示例:页面加载时获取数据
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>created 钩子示例</title>
<style>
.container {
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
.user-list {
list-style: none;
padding: 0;
}
.user-item {
padding: 15px;
margin: 10px 0;
background: #f0f0f0;
border-radius: 5px;
border-left: 4px solid #42b983;
}
.loading {
text-align: center;
padding: 50px;
color: #999;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<h2>用户列表</h2>
<!-- 加载中状态 -->
<div v-if="loading" class="loading">
⏳ 正在加载数据...
</div>
<!-- 用户列表 -->
<ul v-else class="user-list">
<li v-for="user in users" :key="user.id" class="user-item">
<strong>{{ user.name }}</strong> - {{ user.email }}
</li>
</ul>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
users: [], // 用户列表
loading: true // 加载状态
}
},
// created:在组件创建完成时执行
created() {
console.log('created 钩子执行了!');
console.log('此时可以访问 data:', this.users);
// 模拟从后端获取数据
this.fetchUsers();
},
methods: {
fetchUsers() {
// 模拟网络请求(实际项目中用 axios 或 fetch)
setTimeout(() => {
this.users = [
{ id: 1, name: '张三', email: 'zhangsan@example.com' },
{ id: 2, name: '李四', email: 'lisi@example.com' },
{ id: 3, name: '王五', email: 'wangwu@example.com' }
];
this.loading = false;
console.log('数据加载完成!');
}, 2000); // 2 秒后加载完成
}
}
}).mount('#app');
</script>
</body>
</html>
预期效果:
- 页面打开时显示"正在加载数据..."
- 2 秒后显示用户列表
- 打开浏览器控制台,可以看到生命周期钩子的执行日志
关键点:
created中可以访问this.users、this.loading等数据created中不能操作 DOM(因为页面还没渲染)
四、mounted 钩子
什么时候用 mounted?
场景:需要在页面渲染完成后操作 DOM。
常见用途:
- 初始化第三方库(如图表库、地图库)
- 获取 DOM 元素的尺寸
- 自动聚焦输入框
- 操作 canvas、视频播放器等
示例 1:自动聚焦输入框
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>mounted 钩子 - 自动聚焦</title>
<style>
.container {
max-width: 600px;
margin: 100px auto;
padding: 30px;
border: 2px solid #42b983;
border-radius: 10px;
}
input {
width: 100%;
padding: 15px;
font-size: 18px;
border: 2px solid #ddd;
border-radius: 5px;
margin: 10px 0;
}
input:focus {
outline: none;
border-color: #42b983;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<h2>登录</h2>
<p>页面加载后,输入框会自动获得焦点</p>
<!-- ref:给元素一个引用名称 -->
<input
ref="usernameInput"
type="text"
placeholder="用户名(自动聚焦)">
<input type="password" placeholder="密码">
<button>登录</button>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
// mounted:在组件挂载完成后执行
mounted() {
console.log('mounted 钩子执行了!');
console.log('此时可以访问 DOM 元素');
// 通过 $refs 访问元素,并让它获得焦点
this.$refs.usernameInput.focus();
}
}).mount('#app');
</script>
</body>
</html>
预期效果:
- 页面加载完成后,用户名输入框自动获得焦点
- 可以直接开始输入
关键点:
ref="usernameInput":给元素添加引用this.$refs.usernameInput:在 JS 中访问这个元素mounted中可以安全地操作 DOM
示例 2:显示欢迎消息
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>生命周期完整演示</title>
<style>
.container {
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
.log {
background: #f0f0f0;
padding: 15px;
border-radius: 5px;
font-family: monospace;
white-space: pre-line;
max-height: 400px;
overflow-y: auto;
}
.message {
padding: 20px;
background: #e7f7ed;
border-radius: 5px;
margin: 20px 0;
border-left: 4px solid #42b983;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<h2>生命周期钩子演示</h2>
<div class="message">
{{ message }}
</div>
<button @click="count++">点击次数:{{ count }}</button>
<h3>生命周期日志:</h3>
<div class="log">{{ logs.join('\n') }}</div>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
message: '等待中...',
count: 0,
logs: []
}
},
// 创建前(data 还没准备好)
beforeCreate() {
this.logs = []; // 此时还不能访问 this.logs,所以直接赋值
console.log('1. beforeCreate - 实例刚创建');
},
// 创建完成(data 已经准备好)
created() {
this.logs.push('2. created - 数据已初始化');
this.message = '组件已创建,数据准备中...';
console.log('2. created - 可以访问 data 了');
},
// 挂载前(开始渲染,但还没挂载到页面)
beforeMount() {
this.logs.push('3. beforeMount - 准备挂载到页面');
console.log('3. beforeMount - 准备渲染');
},
// 挂载完成(页面已经显示)
mounted() {
this.logs.push('4. mounted - 已挂载到页面');
console.log('4. mounted - 页面已显示,可以操作 DOM');
// 模拟异步操作(如请求数据)
setTimeout(() => {
this.message = '✅ 欢迎来到 Vue 的世界!所有准备工作已完成。';
this.logs.push('→ 数据加载完成');
}, 1500);
},
// 更新前(数据变了,但页面还没更新)
beforeUpdate() {
this.logs.push('5. beforeUpdate - 数据改变,准备更新页面');
console.log('5. beforeUpdate - 准备更新');
},
// 更新完成(页面已经更新)
updated() {
this.logs.push('6. updated - 页面已更新');
console.log('6. updated - 页面更新完成');
}
}).mount('#app');
</script>
</body>
</html>
预期效果:
- 页面加载时,日志依次显示生命周期执行顺序
- 1.5 秒后,显示欢迎消息
- 点击按钮时,触发
beforeUpdate和updated
五、生命周期钩子对比
| 钩子 | 时机 | 能否访问 data | 能否操作 DOM | 常见用途 |
|---|---|---|---|---|
| beforeCreate | 实例刚创建 | ❌ | ❌ | 很少使用 |
| created ⭐ | data 已初始化 | ✅ | ❌ | 请求数据、初始化变量 |
| beforeMount | 准备渲染 | ✅ | ❌ | 很少使用 |
| mounted ⭐ | 页面已渲染 | ✅ | ✅ | 操作 DOM、初始化插件 |
| beforeUpdate | 数据变化 | ✅ | ✅ | 很少使用 |
| updated | 页面已更新 | ✅ | ✅ | 需要在更新后操作 DOM |
| beforeUnmount | 准备销毁 | ✅ | ✅ | 清理定时器、取消订阅 |
| unmounted | 已销毁 | ✅ | ❌ | 最后的清理工作 |
新手记住这两个就够了:
- 📊
created:获取数据 - 🎨
mounted:操作 DOM
六、实战案例:倒计时组件
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>倒计时组件</title>
<style>
.countdown {
text-align: center;
padding: 50px;
}
.countdown .time {
font-size: 72px;
font-weight: bold;
color: #42b983;
margin: 30px 0;
}
.countdown button {
padding: 15px 30px;
font-size: 18px;
margin: 10px;
cursor: pointer;
border: none;
border-radius: 5px;
background: #42b983;
color: white;
}
.countdown button:disabled {
background: #ccc;
cursor: not-allowed;
}
</style>
</head>
<body>
<div id="app">
<div class="countdown">
<h2>⏰ 倒计时器</h2>
<div class="time">{{ seconds }}</div>
<button @click="start" :disabled="isRunning">开始</button>
<button @click="pause" :disabled="!isRunning">暂停</button>
<button @click="reset">重置</button>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
seconds: 60, // 倒计时秒数
isRunning: false, // 是否正在运行
timer: null // 定时器 ID
}
},
methods: {
start() {
if (this.isRunning || this.seconds === 0) return;
this.isRunning = true;
this.timer = setInterval(() => {
this.seconds--;
// 倒计时结束
if (this.seconds === 0) {
this.pause();
alert('⏰ 时间到!');
}
}, 1000);
},
pause() {
this.isRunning = false;
clearInterval(this.timer);
this.timer = null;
},
reset() {
this.pause();
this.seconds = 60;
}
},
// 组件销毁前,清理定时器(避免内存泄漏)
beforeUnmount() {
if (this.timer) {
clearInterval(this.timer);
console.log('定时器已清理');
}
}
}).mount('#app');
</script>
</body>
</html>
预期效果:
- 点击"开始":倒计时开始
- 点击"暂停":倒计时暂停
- 点击"重置":恢复到 60 秒
- 倒计时结束时弹出提示
重要:beforeUnmount 中清理定时器,防止组件销毁后定时器还在运行。
七、本节重点回顾
✅ 生命周期:组件从创建到销毁的过程
✅ 生命周期钩子:在特定时刻自动执行的函数
✅ created:数据初始化完成,常用于请求数据
✅ mounted:页面挂载完成,常用于操作 DOM
✅ beforeUnmount:组件销毁前,常用于清理定时器、取消订阅
记忆口诀:
- 创建阶段:数据准备(created)
- 挂载阶段:页面显示(mounted)
- 更新阶段:数据变化
- 销毁阶段:清理资源(beforeUnmount)
八、练习题
练习 1:页面访问统计
创建一个应用,要求:
- 在
mounted钩子中记录页面加载时间 - 显示"页面已加载 X 秒"(每秒更新)
- 在
beforeUnmount中清理定时器
点击查看答案
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>页面访问统计</title>
</head>
<body>
<div id="app">
<h2>页面访问统计</h2>
<p>页面已加载 {{ seconds }} 秒</p>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
seconds: 0,
timer: null
}
},
mounted() {
// 每秒更新一次
this.timer = setInterval(() => {
this.seconds++;
}, 1000);
},
beforeUnmount() {
// 清理定时器
if (this.timer) {
clearInterval(this.timer);
}
}
}).mount('#app');
</script>
</body>
</html>
练习 2:自动保存功能
创建一个笔记应用,要求:
- 有一个文本输入框
- 在
mounted钩子中,如果 localStorage 有保存的内容,自动加载 - 用户输入时,每 3 秒自动保存到 localStorage
提示:
// 保存数据
localStorage.setItem('note', this.content);
// 读取数据
const saved = localStorage.getItem('note');
点击查看答案
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>自动保存笔记</title>
<style>
.container {
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
textarea {
width: 100%;
height: 300px;
padding: 15px;
font-size: 16px;
border: 2px solid #ddd;
border-radius: 5px;
}
.status {
margin-top: 10px;
color: #42b983;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<h2>📝 自动保存笔记</h2>
<textarea
v-model="content"
placeholder="开始写笔记,每 3 秒自动保存...">
</textarea>
<div class="status">{{ status }}</div>
</div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
content: '',
status: '',
timer: null
}
},
mounted() {
// 加载保存的内容
const saved = localStorage.getItem('note');
if (saved) {
this.content = saved;
this.status = '✅ 已加载保存的笔记';
}
// 每 3 秒自动保存
this.timer = setInterval(() => {
if (this.content) {
localStorage.setItem('note', this.content);
this.status = `✅ 已自动保存(${new Date().toLocaleTimeString()})`;
}
}, 3000);
},
beforeUnmount() {
// 清理定时器
if (this.timer) {
clearInterval(this.timer);
}
}
}).mount('#app');
</script>
</body>
</html>
🎉 恭喜你完成第七节课!
下一节我们将进行:综合实战 - Todo List 应用 —— 把前面学的所有知识点整合起来,做一个完整的应用!