第2章 2.3 生命周期钩子(新版)
🎯 为什么你需要一个"自动执行的剧本"?
想象一下,你开了一家奶茶店。
早上开门 → 你要摆好桌椅、打开灯、准备好吸管(相当于 Vue 的 onMounted)
有人点单 → 你要调配奶茶、递给他(相当于 Vue 的 onUpdated)
晚上关门 → 你要关灯、打扫卫生、处理没卖完的原料(相当于 Vue 的 onUnmounted)
如果你每天都要手动记住做这些事,忙起来肯定会忘。
Vue3 的生命周期钩子就是这样一个"自动执行的剧本"——你提前写好"开门要做什么",Vue 自动在合适的时机帮你执行。
痛点来了:你是不是也遇到过这种情况——组件加载时想获取数据,但不知道放哪个钩子?或者组件更新时想执行某个操作,却不知道该怎么写?
学完这一章,你将学会:
- 在正确的时机做正确的事
- 用 3 个核心钩子解决 80% 的常见场景
- 写出一个自动刷新数据的小工具
🧱 基础:什么是生命周期钩子?
\n\n
\n\n
\n\n 2.3.1 钩子是什么?
钩子(Hook) = 钩子函数 = 回调函数
类比一下:钩子就像闹钟。你提前设定好"早上 7 点叫我起床",到时间闹钟就响,自动执行。
# 这是一个最简单的时间钩子(类比 Vue 的 onMounted)
import time
def on_app_start():
"""应用启动时自动执行的函数"""
print("📦 组件加载完成,我被自动调用了!")
# 模拟 Vue 的 onMounted 钩子调用
print("Vue 组件正在挂载...")
on_app_start() # 这就是"钩子被触发"的时刻
输出:
Vue 组件正在挂载...
📦 组件加载完成,我被自动调用了!
这行
on_app_start()就是"钩子"——你定义好,框架在合适的时机帮你调用。
2.3.2 三个最常用的钩子(组合式 API)
Vue3 组合式 API 提供了 3 个最核心的钩子:
| 钩子 | 什么时候触发 | 类比 |
|---|---|---|
onMounted |
组件挂载到 DOM 后 | 奶茶店开门,把桌椅摆好 |
onUpdated |
组件数据更新后 | 有人点了新单,奶茶做好了 |
onUnmounted |
组件卸载前 | 奶茶店关门,要收拾残局 |
# 模拟 Vue 组合式 API 的三个钩子
import time
class VueComponent:
def __init__(self):
# 1. onMounted - 挂载阶段(开店)
print("🏪 奶茶店正在开门...")
self.on_mounted() # Vue 自动调用这个
# 模拟数据
self.order_count = 0
def on_mounted(self):
"""组件挂载后执行(开店准备)"""
print("✅ onMounted: 桌椅摆好、灯打开、吸管准备好")
def add_order(self, count):
"""模拟数据更新"""
self.order_count += count
# 2. onUpdated - 更新阶段(有人点单)
print(f"📝 新订单来了!点了 {count} 杯")
self.on_updated()
def on_updated(self):
"""数据更新后执行(做好奶茶)"""
print(f"✅ onUpdated: 奶茶做好了,当前共 {self.order_count} 杯")
def close(self):
"""模拟组件卸载(关门)"""
# 3. onUnmounted - 卸载阶段(打烊)
print("🌙 奶茶店准备打烊...")
self.on_unmounted()
def on_unmounted(self):
"""组件卸载前执行(收拾店铺)"""
print("✅ onUnmounted: 关灯、打扫、处理剩余原料")
print("📦 组件已销毁")
# 运行演示
shop = VueComponent()
shop.add_order(2)
shop.add_order(3)
shop.close()
输出:
🏪 奶茶店正在开门...
✅ onMounted: 桌椅摆好、灯打开、吸管准备好
📝 新订单来了!点了 2 杯
✅ onUpdated: 奶茶做好了,当前共 2 杯
📝 新订单来了!点了 3 杯
✅ onUpdated: 奶茶做好了,当前共 5 杯
🌙 奶茶店准备打烊...
✅ onUnmounted: 关灯、打扫、处理剩余原料
📦 组件已销毁
注意! 很多新手把更新逻辑写到
onMounted里,结果每次数据变化都会重复执行——这就是没搞清楚钩子职责的区别。
2.3.3 钩子的执行顺序
Vue 的生命周期是严格按顺序执行的:
# 模拟 Vue 组件的完整生命周期
def vue_component_lifecycle():
"""模拟一个 Vue 组件的完整生命周期"""
print("=" * 40)
print("📍 阶段1: setup() - 创建响应式状态")
state = {"count": 0}
print(f" 创建状态: {state}")
print("\n📍 阶段2: onMounted() - 挂载完成")
print(" ✅ DOM 已渲染,可以操作页面元素了")
print("\n📍 阶段3: 数据变化,触发 onUpdated()")
state["count"] = 1
print(f" 状态更新: {state}")
print(" ✅ 重新渲染 DOM")
print("\n📍 阶段4: 又一次数据变化,再次触发 onUpdated()")
state["count"] = 2
print(f" 状态更新: {state}")
print(" ✅ 再次重新渲染 DOM")
print("\n📍 阶段5: onUnmounted() - 组件销毁")
print(" ✅ 清理定时器、解除事件监听")
vue_component_lifecycle()
输出:
========================================
📍 阶段1: setup() - 创建响应式状态
建状态: {'count': 0}
📍 阶段2: onMounted() - 挂载完成
DOM 已渲染,可以操作页面元素了
📍 阶段3: 数据变化,触发 onUpdated()
态更新: {'count': 1}
重新渲染 DOM
📍 阶段4: 又一次数据变化,再次触发 onUpdated()
态更新: {'count': 2}
再次重新渲染 DOM
📍 阶段5: onUnmounted() - 组件销毁
清理定时器、解除事件监听
🔥 实战:3 个递进小项目
📦 项目 1:自动刷新天气的“小天气站”(5 分钟)
场景:你想做一个每 5 秒自动刷新天气数据的小工具。
import time
import random
class WeatherStation:
"""模拟一个天气数据获取组件"""
def __init__(self, city):
self.city = city
self.weather_data = None
self.timer = None
# onMounted: 组件挂载时,自动获取第一次数据
self.on_mounted()
def on_mounted(self):
"""组件挂载后执行:开始定时刷新"""
print(f"🏠 onMounted: {self.city}天气站已启动")
self.fetch_weather() # 立即获取一次
self.start_auto_refresh() # 开始定时刷新
def fetch_weather(self):
"""获取天气数据"""
weathers = ["☀️ 晴", "🌧️ 雨", "⛅ 多云", "❄️ 雪"]
temp = random.randint(-5, 35)
weather = random.choice(weathers)
self.weather_data = {"city": self.city, "weather": weather, "temp": temp}
print(f"📡 获取天气: {self.city} {weather} {temp}°C")
def on_updated(self):
"""数据更新后执行:显示新数据"""
print(f"🔄 onUpdated: 天气数据已刷新 -> {self.weather_data}")
def start_auto_refresh(self):
"""模拟定时刷新(每5秒)"""
print("⏰ 开始5秒自动刷新...")
self.fetch_weather()
self.on_updated()
def on_unmounted(self):
"""组件卸载时:清理定时器"""
print("🧹 onUnmounted: 清理定时器,停止刷新")
self.timer = None
# 运行天气站
station = WeatherStation("北京")
print("\n等待5秒...\n")
time.sleep(1) # 简化版,实际项目中 Vue 会自动处理定时器
station.on_unmounted()
预期输出:
🏠 onMounted: 北京天气站已启动
📡 获取天气: 北京 🌧️ 雨 18°C
⏰ 开始5秒自动刷新...
📡 获取天气: 北京 ☀️ 晴 22°C
🔄 onUpdated: 天气数据已刷新 -> {'city': '北京', 'weather': '☀️ 晴', 'temp': 22}
🧹 onUnmounted: 清理定时器,停止刷新
一句话解释:这个项目展示了
onMounted负责初始化,onUpdated负责数据变化后的处理,onUnmounted负责清理工作。
📊 项目 2:股票数据监控面板(15 分钟)
场景:从 JSON 数据读取股票列表,实时监控涨跌,自动报警。
import time
import random
class StockMonitor:
"""股票监控系统"""
def __init__(self):
self.stocks = [] # 股票数据
self.alerts = [] # 报警记录
# onMounted: 加载数据
self.on_mounted()
def on_mounted(self):
"""组件挂载:从后端获取股票数据"""
print("📡 onMounted: 正在加载股票数据...")
self.load_stocks()
self.start_monitoring()
def load_stocks(self):
"""模拟从 API 加载股票数据"""
self.stocks = [
{"code": "AAPL", "name": "苹果", "price": 175.5, "change": 0},
{"code": "GOOGL", "name": "谷歌", "price": 140.2, "change": 0},
{"code": "TSLA", "name": "特斯拉", "price": 242.8, "change": 0},
]
print(f"✅ 加载了 {len(self.stocks)} 只股票")
def on_updated(self):
"""数据更新后:显示最新行情"""
self.display_stocks()
self.check_alerts()
def start_monitoring(self):
"""模拟实时监控(每3秒更新一次)"""
print("📊 开始监控股票行情(按 Ctrl+C 停止)...\n")
for i in range(3): # 模拟3次更新
self.update_prices()
self.on_updated()
if i < 2:
time.sleep(1)
def update_prices(self):
"""模拟价格变动"""
for stock in self.stocks:
# 随机涨跌 -3% ~ +3%
change_pct = random.uniform(-0.03, 0.03)
old_price = stock["price"]
stock["price"] = round(old_price * (1 + change_pct), 2)
stock["change"] = round(change_pct * 100, 2)
def display_stocks(self):
"""显示股票行情"""
print("-" * 50)
print(f"{'代码':<8}{'名称':<8}{'价格':<10}{'涨跌':<10}")
print("-" * 50)
for stock in self.stocks:
change_sign = "+" if stock["change"] > 0 else ""
emoji = "🔴" if stock["change"] < 0 else "🟢"
print(f"{stock['code']:<8}{stock['name']:<8}${stock['price']:<9}{emoji}{change_sign}{stock['change']}%")
print("-" * 50)
def check_alerts(self):
"""检查是否触发报警(涨跌超过2%)"""
for stock in self.stocks:
if abs(stock["change"]) > 2:
alert_msg = f"🚨 报警: {stock['name']}({stock['code']}) 波动 {stock['change']}%"
self.alerts.append(alert_msg)
print(alert_msg)
def on_unmounted(self):
"""组件卸载:停止监控"""
print(f"\n🧹 onUnmounted: 停止监控,共触发 {len(self.alerts)} 次报警")
self.stocks = []
self.alerts = []
# 运行股票监控
monitor = StockMonitor()
monitor.on_unmounted()
预期输出:
📡 onMounted: 正在加载股票数据...
✅ 加载了 3 只股票
📊 开始监控股票行情(按 Ctrl+C 停止)...
--------------------------------------------------
代码 名称 价格 涨跌
--------------------------------------------------
AAPL 苹果 $174.2 🔴-0.74%
GOOGL 谷歌 $142.5 🟢+1.64%
TSLA 特斯拉 $238.9 🔴-1.61%
--------------------------------------------------
--------------------------------------------------
代码 名称 价格 涨跌
--------------------------------------------------
AAPL 苹果 $175.1 🟢+0.52%
GOOGL 谷歌 $143.8 🟢+0.91%
TSLA 特斯拉 $244.1 🟢+2.18%
--------------------------------------------------
🚨 报警: 特斯拉(TSLA) 波动 2.18%
...
🧹 onUnmounted: 停止监控,共触发 2 次报警
一句话解释:
onMounted负责初始化数据,onUpdated负责数据变化后的 UI 更新和业务逻辑(报警检查),onUnmounted负责清理资源。
📝 项目 3:待办事项管理器(15 分钟)
场景:做一个带本地存储的待办清单,支持添加、完成、删除,刷新页面不丢失数据。
import json
import time
class TodoList:
"""待办事项管理器 - 组合式 API 生命周期实战"""
def __init__(self):
self.todos = [] # 待办列表
self.storage_key = "my_todos" # localStorage 键名
# onMounted: 从本地存储加载数据
self.on_mounted()
def on_mounted(self):
"""组件挂载:恢复已保存的待办事项"""
print("📂 onMounted: 正在恢复待办事项...")
self.load_from_storage()
self.display_todos()
def on_updated(self):
"""数据更新后:保存到本地存储"""
self.save_to_storage()
def load_from_storage(self):
"""模拟从 localStorage 读取数据"""
saved = [
{"id": 1, "text": "完成 Vue3 作业", "done": True},
{"id": 2, "text": "买菜做饭", "done": False},
{"id": 3, "text": "给妈妈打电话", "done": False},
]
self.todos = saved
print(f"✅ 从本地存储恢复了 {len(self.todos)} 条待办")
def save_to_storage(self):
"""模拟保存到 localStorage"""
print("💾 保存到本地存储...")
def add_todo(self, text):
"""添加新待办"""
new_id = max([t["id"] for t in self.todos], default=0) + 1
todo = {"id": new_id, "text": text, "done": False}
self.todos.append(todo)
print(f"➕ 添加: {text}")
self.on_updated()
self.display_todos()
def toggle_todo(self, todo_id):
"""切换完成状态"""
for todo in self.todos:
if todo["id"] == todo_id:
todo["done"] = not todo["done"]
status = "✅ 完成" if todo["done"] else "⬜ 未完成"
print(f"🔄 切换 {todo['text']} -> {status}")
self.on_updated()
self.display_todos()
def delete_todo(self, todo_id):
"""删除待办"""
self.todos = [t for t in self.todos if t["id"] != todo_id]
print(f"🗑️ 删除 ID={todo_id} 的待办")
self.on_updated()
self.display_todos()
def display_todos(self):
"""显示待办列表"""
print("\n" + "=" * 40)
print(f"📝 我的待办清单 ({len(self.todos)} 项)")
print("=" * 40)
if not self.todos:
print("空空如也,休息一下吧 ☕")
else:
for todo in self.todos:
checkbox = "✅" if todo["done"] else "⬜"
text = todo["text"]
if todo["done"]:
text = f"~~{text}~~" # 删除线
print(f" {checkbox} [{todo['id']}] {text}")
# 统计
done_count = sum(1 for t in self.todos if t["done"])
print(f"\n进度: {done_count}/{len(self.todos)} 完成")
print("=" * 40 + "\n")
def on_unmounted(self):
"""组件卸载:最后保存一次"""
print("👋 onUnmounted: 正在保存并退出...")
self.save_to_storage()
print("✅ 待办事项已保存,下次见!")
# 运行待办清单
print("🚀 启动待办事项管理器\n")
todo_app = TodoList()
# 模拟用户操作
todo_app.add_todo("学习 Python 生命周期")
todo_app.toggle_todo(2) # 买菜做饭 -> 完成
todo_app.toggle_todo(1) # 取消完成作业
todo_app.add_todo("看一集动画片")
todo_app.delete_todo(3) # 删除给妈妈打电话
# 模拟页面关闭
print("🚪 关闭页面...")
time.sleep(0.5)
todo_app.on_unmounted()
预期输出:
🚀 启动待办事项管理器
📂 onMounted: 正在恢复待办事项...
✅ 从本地存储恢复了 3 条待办
========================================
📝 我的待办清单 (3 项)
========================================
✅ [1] ~~完成 Vue3 作业~~
⬜ [2] 买菜做饭
⬜ [3] 给妈妈打电话
进度: 1/3 完成
========================================
➕ 添加: 学习 Python 生命周期
💾 保存到本地存储...
========================================
📝 我的待办清单 (4 项)
========================================
✅ [1] ~~完成 Vue3 作业~~
⬜ [2] 买菜做饭
⬜ [3] 给妈妈打电话
⬜ [4] 学习 Python 生命周期
进度: 1/4 完成
========================================
🔄 切换 买菜做饭 -> ✅ 完成
💾 保存到本地存储...
...
🚪 关闭页面...
👋 onUnmounted: 正在保存并退出...
💾 保存到本地存储...
✅ 待办事项已保存,下次见!
一句话解释:这个项目完整展示了
onMounted(初始化/恢复数据)、onUpdated(数据变化时保存)、onUnmounted(退出时最终保存)的协作关系。
💪 进阶:新手最容易踩的 5 个坑
❌ 坑 1:在 onMounted 里调用还没初始化的变量
# ❌ 错误示例
class BadComponent:
def __init__(self):
self.on_mounted()
def on_mounted(self):
# 错误:这里用了 self.data,但还没定义
print(self.data) # AttributeError!
# ✅ 正确示例
class GoodComponent:
def __init__(self):
self.data = "初始化完成" # 先定义,再使用
self.on_mounted()
def on_mounted(self):
print(self.data) # ✅ 正常输出
❌ 坑 2:在 onMounted 里写太多同步代码,阻塞页面加载
# ❌ 错误示例:onMounted 里做耗时操作
def on_mounted(self):
print("开始下载...")
for i in range(10000): # 模拟耗时操作
download_file(i)
print("下载完成") # 页面要等很久才能显示
# ✅ 正确示例:用异步或延迟加载
def on_mounted(self):
print("页面已显示,先给用户看东西")
# 用定时器在后台加载,不阻塞 UI
self.load_in_background()
❌ 坑 3:忘记在 onUnmounted 里清理定时器(内存泄漏!)
import time
class TimerComponent:
def __init__(self):
self.timer = None
self.on_mounted()
def on_mounted(self):
# ❌ 错误:没保存定时器引用,无法清理
time.set_interval(self.do_something, 1000)
def on_unmounted(self):
# ❌ 错误:无法停止,因为没有引用
pass
# ✅ 正确示例
class TimerComponentFixed:
def __init__(self):
self.timer = None
self.on_mounted()
def on_mounted(self):
# 保存定时器引用
self.timer = time.set_interval(self.do_something, 1000)
print("⏰ 定时器已启动")
def on_unmounted(self):
# ✅ 清理定时器
if self.timer:
self.timer.stop()
print("🧹 定时器已清理,防止内存泄漏")
❌ 坑 4:在 onUpdated 里修改状态,导致无限循环
# ❌ 错误示例:onUpdated 里修改数据,触发新的 onUpdated
class BadLoop:
def __init__(self):
self.count = 0
self.on_mounted()
def on_updated(self):
self.count += 1 # 修改状态
# 又触发 onUpdated -> 无限循环!
# ✅ 正确示例:只在特定条件下更新
class GoodLoop:
def __init__(self):
self.count = 0
self.on_mounted()
def on_updated(self):
if self.count < 5: # 加个上限
self.count += 1
print(f"计数: {self.count}")
❌ 坑 5:混淆 onMounted 和 onUpdated 的职责
| 场景 | 用哪个钩子 |
|---|---|
| 获取初始数据 | onMounted |
| 初始化第三方库(如图表库) | onMounted |
| 数据变化后重新获取数据 | onUpdated |
| 更新 DOM 操作(如聚焦输入框) | onUpdated |
# 典型错误:把数据获取写到 onUpdated
class BadExample:
def on_updated(self):
self.fetch_data() # ❌ 每次数据变化都会重新获取,浪费!
# 正确做法:只在 onMounted 获取初始数据
class GoodExample:
def on_mounted(self):
self.fetch_data() # ✅ 只在初始化时获取一次
🛠️ 调试技巧:print 大法
def on_mounted(self):
print(f"🔵 [onMounted] 组件挂载,当前数据: {self.__dict__}")
def on_updated(self):
print(f"🟡 [onUpdated] 数据已更新: {self.__dict__}")
def on_unmounted(self):
print(f"🔴 [onUnmounted] 组件即将销毁")
注意! 生产环境记得删除这些 debug 日志,或者用
logging模块代替 print。
✏️ 练习题
练习 1(2 分钟):认识钩子
- 输入:运行以下代码,观察输出顺序
- 预期输出:按
onMounted -> onUpdated x2 -> onUnmounted顺序打印 - 提示:关注打印的顺序
import time
class TestLifecycle:
def __init__(self):
self.data = 0
self.on_mounted()
self.update()
self.update()
self.on_unmounted()
def on_mounted(self):
print("1️⃣ onMounted 被调用")
def on_updated(self):
print(f"2️⃣ onUpdated 被调用,data={self.data}")
def update(self):
self.data += 1
self.on_updated()
def on_unmounted(self):
print("3️⃣ onUnmounted 被调用")
TestLifecycle()
练习 2(2 分钟):添加判断
- 输入:在练习 1 的基础上,只在
data > 1时打印 - 预期输出:跳过 data=1 的那次更新
- 提示:在
on_updated里加个 if 判断
练习 3(3 分钟):处理新数据
- 输入:用项目 2 的方式,创建一个监控两只股票(AAPL、TSLA)的代码
- 预期输出:显示两只股票的行情和涨跌
- 提示:修改
load_stocks()里的数据列表
练习 4(3 分钟):串接两个项目
- 输入:把股票监控和待办清单组合,股票涨了就在待办里添加"庆祝一下!"
- 预期输出:当某只股票涨幅 > 1% 时,自动添加待办
- 提示:在
check_alerts()里调用add_todo()
练习 5(5 分钟):分析报错
- 输入:以下代码运行会报错,找出原因并修复
class BrokenComponent:
def __init__(self):
self.on_mounted()
def on_updated(self):
print(self.name) # 报错:name 不存在
def on_mounted(self):
self.name = "小明"
self.on_updated() # 这里调用了 on_updated
# 运行
bc = BrokenComponent()
- 预期输出:
小明 - 提示:检查
on_updated在name定义之前还是之后被调用
📋 作业:做一个「学习进度追踪器」
需求描述:
做一个命令行版的学习进度追踪器,记录你每天学习 Vue3 的时长,统计数据可视化。
功能点:
1. 添加学习记录:输入今天学习了多少分钟
2. 查看统计:显示总学习时长、平均每日学习时长
3. 里程碑提醒:学习满 30 分钟显示"🎉 今天达标了!"
4. 数据持久化:用 JSON 文件保存,关闭后再打开不丢失
加分项:
1. 支持删除某天的记录
2. 显示学习时长柱状图(用文字模拟)
验收标准:
- 能添加记录并保存到文件
- 能从文件恢复数据
- 关闭程序后重新打开,数据依然在
提交方式:评论区贴代码或 GitHub 链接
📚 总结 + 资源
一句话总结本文学到的 3 个核心点
onMounted= 初始化阶段(开店),只执行一次,适合获取初始数据、初始化第三方库onUpdated= 更新阶段(有人点单),每次数据变化都会触发,适合处理副作用onUnmounted= 销毁阶段(打烊),只执行一次,必须清理定时器/事件监听器
推荐延伸学习资源
- Vue3 官方文档 - 生命周期钩子 (英文原版,最权威)
- 《Vue3 设计与实现》- 生命周期章节详细剖析了 Vue3 的响应式系统
- Vue3 Composition API 入门视频 - 适合视觉学习者
互动钩子:
你在写项目时,有没有在某个生命周期钩子里踩过坑?是
onMounted和onUpdated傻傻分不清?还是忘了清理定时器?评论区聊聊,老粉优先回复!
下章预告:
学会了生命周期钩子,你已经能在"合适的时机做合适的事"。但如果遇到爷孙组件(爷 → 爸 → 孙)之间要传值,props 一层层往下传太麻烦了……下一章我们要聊的
provide/inject,就是来解决这个问题的!🎯

评论(0)