跳到主要内容

第 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>

预期效果

  1. 页面打开时显示"正在加载数据..."
  2. 2 秒后显示用户列表
  3. 打开浏览器控制台,可以看到生命周期钩子的执行日志

关键点

  • created 中可以访问 this.usersthis.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. 页面加载时,日志依次显示生命周期执行顺序
  2. 1.5 秒后,显示欢迎消息
  3. 点击按钮时,触发 beforeUpdateupdated

五、生命周期钩子对比

钩子时机能否访问 data能否操作 DOM常见用途
beforeCreate实例刚创建很少使用
createddata 已初始化请求数据、初始化变量
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:页面访问统计

创建一个应用,要求:

  1. mounted 钩子中记录页面加载时间
  2. 显示"页面已加载 X 秒"(每秒更新)
  3. 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:自动保存功能

创建一个笔记应用,要求:

  1. 有一个文本输入框
  2. mounted 钩子中,如果 localStorage 有保存的内容,自动加载
  3. 用户输入时,每 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 应用 —— 把前面学的所有知识点整合起来,做一个完整的应用!