第2章 2.3 生命周期钩子(新版)

🎯 为什么你需要一个"自动执行的剧本"?

想象一下,你开了一家奶茶店。

早上开门 → 你要摆好桌椅、打开灯、准备好吸管(相当于 Vue 的 onMounted
有人点单 → 你要调配奶茶、递给他(相当于 Vue 的 onUpdated
晚上关门 → 你要关灯、打扫卫生、处理没卖完的原料(相当于 Vue 的 onUnmounted

如果你每天都要手动记住做这些事,忙起来肯定会忘。

Vue3 的生命周期钩子就是这样一个"自动执行的剧本"——你提前写好"开门要做什么",Vue 自动在合适的时机帮你执行。

痛点来了:你是不是也遇到过这种情况——组件加载时想获取数据,但不知道放哪个钩子?或者组件更新时想执行某个操作,却不知道该怎么写?

学完这一章,你将学会:
- 在正确的时机做正确的事
- 用 3 个核心钩子解决 80% 的常见场景
- 写出一个自动刷新数据的小工具


🧱 基础:什么是生命周期钩子?

\n\nSimple tech illustration expla\n\nAI comic creation scene, creat\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:混淆 onMountedonUpdated 的职责

场景 用哪个钩子
获取初始数据 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_updatedname 定义之前还是之后被调用

📋 作业:做一个「学习进度追踪器」

需求描述
做一个命令行版的学习进度追踪器,记录你每天学习 Vue3 的时长,统计数据可视化。

功能点
1. 添加学习记录:输入今天学习了多少分钟
2. 查看统计:显示总学习时长、平均每日学习时长
3. 里程碑提醒:学习满 30 分钟显示"🎉 今天达标了!"
4. 数据持久化:用 JSON 文件保存,关闭后再打开不丢失

加分项
1. 支持删除某天的记录
2. 显示学习时长柱状图(用文字模拟)

验收标准
- 能添加记录并保存到文件
- 能从文件恢复数据
- 关闭程序后重新打开,数据依然在

提交方式:评论区贴代码或 GitHub 链接


📚 总结 + 资源

一句话总结本文学到的 3 个核心点

  1. onMounted = 初始化阶段(开店),只执行一次,适合获取初始数据、初始化第三方库
  2. onUpdated = 更新阶段(有人点单),每次数据变化都会触发,适合处理副作用
  3. onUnmounted = 销毁阶段(打烊),只执行一次,必须清理定时器/事件监听器

推荐延伸学习资源

  1. Vue3 官方文档 - 生命周期钩子 (英文原版,最权威)
  2. 《Vue3 设计与实现》- 生命周期章节详细剖析了 Vue3 的响应式系统
  3. Vue3 Composition API 入门视频 - 适合视觉学习者

互动钩子

你在写项目时,有没有在某个生命周期钩子里踩过坑?是 onMountedonUpdated 傻傻分不清?还是忘了清理定时器?评论区聊聊,老粉优先回复!


下章预告

学会了生命周期钩子,你已经能在"合适的时机做合适的事"。但如果遇到爷孙组件(爷 → 爸 → 孙)之间要传值,props 一层层往下传太麻烦了……下一章我们要聊的 provide/inject,就是来解决这个问题的!🎯

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。