第2章 2.1 页面生命周期
📌 前情提要:上一章我们学会了用 rpx 做自适应布局,终于不用再为「这台手机大、那台手机小」抓狂了。但光有样式还不够——页面加载时要做数据请求、页面显示时要更新状态、页面离开时要清理资源,这些「什么时候该干什么」的事情,谁来管?答案就是生命周期。
🎯 开场 3 分钟:为什么要学这个?
你有没有遇到过这种情况:
- 场景 1:小程序刚打开白屏了好几秒,不知道在干嘛
- 场景 2:页面切到后台再切回来,数据居然没了
- 场景 3:返回上一页时,内存占用越来越高
这些问题,生命周期能帮你从根本上理解 + 解决。
学完这章,你会:
1. 知道小程序从打开到关闭,经历了哪些「人生阶段」
2. 在正确的阶段做正确的事(比如别在 onLoad 里写动画)
3. 避免「内存泄漏」「数据不更新」这些经典坑
🧱 基础 25 分钟:核心概念
什么是生命周期?
类比:就像一个人从出生到死亡,有婴儿期、童年、青年、中年、老年……每\n\n
\n\n
\n\n个阶段适合做不同的事。婴儿期适合取名字,老年期适合退休养老,你要是反过来,在婴儿期让人退休,在老年期让人取名字,肯定乱套。
小程序也一样,页面从加载到销毁,有几个关键节点:
加载 → 显示 → 渲染完成 → 隐藏 → 卸载
每个节点就是一个「生命周期函数」,你可以在这些函数里写对应的逻辑。
uniApp 的页面生命周期(Vue3)
在 uniApp(Vue3)中,页面生命周期主要有 4 个:
| 生命周期 | 什么时候调用 | 适合做什么 |
|---|---|---|
onLoad |
页面加载时(只调用一次) | 接收参数、初始化数据、请求初始接口 |
onShow |
页面显示时(每次切回来都调用) | 刷新数据、检测登录状态 |
onReady |
页面渲染完成时 | 获取元素信息(DOM操作)、启动动画 |
onUnload |
页面卸载时 | 清理定时器、解绑事件、释放资源 |
代码怎么写?
在 Vue3 的 <script setup> 语法下:
<script setup>
import { onLoad, onShow, onReady, onUnload } from '@dcloudio/uni-app'
// 页面加载时调用(只一次)
onLoad(() => {
console.log('页面开始加载了')
// 这里适合:获取 url 参数、初始化请求
})
// 页面显示时调用(每次切回来都调用)
onShow(() => {
console.log('页面显示了')
// 这里适合:刷新动态数据、检测登录状态
})
// 页面渲染完成时调用
onReady(() => {
console.log('页面渲染完成了')
// 这里适合:操作 DOM、启动动画、计算元素位置
})
// 页面卸载时调用
onUnload(() => {
console.log('页面要卸载了')
// 这里适合:清理定时器、解绑事件监听
})
</script>
每段代码下面跟 1 句话解释:这 4 个生命周期函数,分别在页面的不同时机被自动调用。
生命周期执行顺序
一个页面的完整生命周期是这样的:
1. onLoad(页面加载,只一次)
↓
2. onShow(页面显示)
↓
3. onReady(渲染完成)
↓
... 页面在这里正常显示 ...
↓
4. 页面隐藏(切到其他页面)
↓(切回来)
5. onShow(再次显示)
↓
... 反复显示/隐藏 ...
↓
6. onUnload(页面关闭/卸载)
重要规律:
- onLoad 只执行一次,是「初始化」的好时机
- onShow 每次可见都执行,适合「刷新」数据
- onUnload 一定要清理资源,否则内存泄漏
生活类比:想象你在开一家奶茶店
| 生命周期 | 开奶茶店的对应行为 |
|---|---|
onLoad |
装修店面、进货、招聘员工(一次性准备) |
onShow |
开门营业、迎接客人(每天都要做的事) |
onReady |
设备调试完成、可以做奶茶了(准备就绪) |
onUnload |
关店、销毁库存、遣散员工(清理资源) |
接收页面参数(url 传参)
页面之间跳转时,经常需要传参数,比如从列表页跳到详情页:
// 列表页:跳转到详情页,携带 id=123
uni.navigateTo({
url: '/pages/detail/detail?id=123&name=奶茶'
})
// 详情页:在 onLoad 里接收参数
onLoad((options) => {
console.log('收到的参数:', options)
// 输出:{ id: '123', name: '奶茶' }
// 用这些参数请求详情数据
fetchDetail(options.id)
})
onLoad的options参数,包含了从 url 里解析出来的所有参数。
小白的经典疑问
Q:onLoad 和 onShow 有什么区别?
简单说:
- onLoad = 页面「出生」,只执行一次
- onShow = 页面「出现在眼前」,每次可见都执行
类比:你打开冰箱(onLoad 一次),然后关上、再打开(onShow 多次)。如果你在「打开冰箱」时往里面放东西,onLoad 放一次就够了;如果你要检查冰箱里的东西是否过期,每次打开都要检查,用 onShow。
🔥 实战 35 分钟:3 个递进的小项目
项目 1:生命周期日志小工具(5 分钟)
目标:在页面上展示各个生命周期函数的触发时机,看得见摸得着。
完整代码:
<!-- pages/demo1/demo1.vue -->
<template>
<view class="container">
<text class="title">生命周期日志</text>
<button @tap="jumpToDetail">跳转到详情页</button>
<view class="log-box">
<text class="log-title">日志:</text>
<text v-for="(log, index) in logs" :key="index" class="log-item">
{{ log }}
</text>
</view>
</view>
</template>
<script setup>
import { onLoad, onShow, onReady, onUnload } from '@dcloudio/uni-app'
import { ref } from 'vue'
const logs = ref([])
const addLog = (msg) => {
const time = new Date().toLocaleTimeString()
logs.value.push(`[${time}] ${msg}`)
}
onLoad(() => {
addLog('onLoad - 页面加载')
})
onShow(() => {
addLog('onShow - 页面显示')
})
onReady(() => {
addLog('onReady - 渲染完成')
})
onUnload(() => {
addLog('onUnload - 页面卸载')
})
const jumpToDetail = () => {
uni.navigateTo({
url: '/pages/demo2/demo2?from=demo1'
})
}
</script>
<style>
.container {
padding: 20px;
}
.title {
font-size: 18px;
font-weight: bold;
display: block;
margin-bottom: 20px;
}
.log-box {
margin-top: 20px;
padding: 10px;
background: #f5f5f5;
border-radius: 8px;
}
.log-item {
display: block;
font-size: 12px;
color: #333;
line-height: 1.8;
}
</style>
预期输出:打开页面时,日志按顺序显示 onLoad → onShow → onReady。
解释:这个小工具帮你「看见」生命周期的执行顺序,以后遇到「为什么我的数据不刷新」这种问题,就知道该在哪个生命周期里找原因了。
项目 2:详情页数据拉取(15 分钟)
目标:从列表页跳转到详情页,携带 ID,在 onLoad 里根据 ID 请求数据。
列表页:
<!-- pages/index/index.vue -->
<template>
<view class="container">
<text class="title">商品列表</text>
<view v-for="item in productList" :key="item.id" class="product-item" @tap="goDetail(item.id)">
<text>{{ item.name }} - ¥{{ item.price }}</text>
</view>
</view>
</template>
<script setup>
import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue'
const productList = ref([])
onLoad(() => {
// 模拟从服务器获取商品列表
productList.value = [
{ id: 1, name: '矿泉水', price: 2 },
{ id: 2, name: '方便面', price: 5 },
{ id: 3, name: '充电宝', price: 89 },
]
})
const goDetail = (id) => {
uni.navigateTo({
url: `/pages/detail/detail?id=${id}`
})
}
</script>
<style>
.product-item {
padding: 15px;
border-bottom: 1px solid #eee;
}
</style>
详情页:
<!-- pages/detail/detail.vue -->
<template>
<view class="container">
<text class="title">{{ product.name }}</text>
<text class="price">¥{{ product.price }}</text>
<text class="desc">{{ product.description }}</text>
<button @tap="goBack">返回列表</button>
</view>
</template>
<script setup>
import { onLoad } from '@dcloudio/uni-app'
import { ref } from 'vue'
const product = ref({
name: '加载中...',
price: 0,
description: ''
})
onLoad((options) => {
const productId = options.id
console.log('收到商品ID:', productId)
// 模拟根据 ID 请求详情数据
setTimeout(() => {
const db = {
1: { name: '矿泉水', price: 2, description: '农夫山泉,550ml' },
2: { name: '方便面', price: 5, description: '康师傅红烧牛肉面' },
3: { name: '充电宝', price: 89, description: '10000mAh 大容量' },
}
product.value = db[productId] || { name: '未找到', price: 0, description: '' }
}, 500)
})
const goBack = () => {
uni.navigateBack()
}
</script>
<style>
.container {
padding: 20px;
}
.price {
display: block;
color: #ff6b6b;
font-size: 24px;
margin: 10px 0;
}
.desc {
display: block;
color: #666;
margin: 20px 0;
}
</style>
预期输出:点击列表中的「矿泉水」,进入详情页显示「矿泉水 - ¥2 - 农夫山泉,550ml」。
解释:在 onLoad 里接收 options.id,根据 ID 请求对应数据,这是小程序开发最常见的模式。
项目 3:待办清单(带本地缓存)(15 分钟)
目标:做一个可以添加、完成、删除待办事项的小工具,刷新页面后数据不丢失。
完整代码:
<!-- pages/todo/todo.vue -->
<template>
<view class="container">
<text class="title">我的待办清单</text>
<!-- 输入区域 -->
<view class="input-row">
<input v-model="newTask" placeholder="输入新任务..." class="task-input" />
<button @tap="addTask" size="mini">添加</button>
</view>
<!-- 任务列表 -->
<view v-for="(task, index) in tasks" :key="index" class="task-item">
<checkbox :checked="task.done" @tap="toggleTask(index)" />
<text :class="{ 'done': task.done }">{{ task.text }}</text>
<text class="delete-btn" @tap="deleteTask(index)">删除</text>
</view>
<text class="stats">共 {{ tasks.length }} 项,已完成 {{ doneCount }} 项</text>
</view>
</template>
<script setup>
import { onLoad, onUnload } from '@dcloudio/uni-app'
import { ref, computed } from 'vue'
const tasks = ref([])
const newTask = ref('')
// 计算已完成数量
const doneCount = computed(() => tasks.value.filter(t => t.done).length)
// 页面加载时读取本地存储
onLoad(() => {
const saved = uni.getStorageSync('myTasks')
if (saved) {
tasks.value = JSON.parse(saved)
}
})
// 页面卸载时保存到本地存储
onUnload(() => {
uni.setStorageSync('myTasks', JSON.stringify(tasks.value))
})
const addTask = () => {
if (!newTask.value.trim()) return
tasks.value.push({
text: newTask.value,
done: false
})
newTask.value = ''
}
const toggleTask = (index) => {
tasks.value[index].done = !tasks.value[index].done
}
const deleteTask = (index) => {
tasks.value.splice(index, 1)
}
</script>
<style>
.container {
padding: 20px;
}
.title {
font-size: 20px;
font-weight: bold;
display: block;
margin-bottom: 20px;
}
.input-row {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.task-input {
flex: 1;
border: 1px solid #ddd;
padding: 8px;
border-radius: 4px;
}
.task-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 0;
border-bottom: 1px solid #eee;
}
.done {
text-decoration: line-through;
color: #999;
}
.delete-btn {
margin-left: auto;
color: #ff6b6b;
}
.stats {
display: block;
margin-top: 20px;
color: #666;
font-size: 14px;
}
</style>
预期输出:
- 添加「买牛奶」,列表显示「买牛奶」(未完成)
- 点击checkbox,「买牛奶」划线显示
- 点击「删除」,任务消失
- 退出页面再进来,任务还在
解释:onLoad 读取本地缓存,onUnload 保存本地缓存,这样用户的待办事项就不会「页面一关就消失」了。
💪 进阶 20 分钟:常见坑 + 性能小贴士
坑 1:定时器没有清理 ❌ → ✅
// ❌ 错误:在 onLoad 里创建定时器,但没有清理
onLoad(() => {
this.timer = setInterval(() => {
this.updateData()
}, 1000)
})
// 切换页面时,定时器继续跑,内存越来越高
// ✅ 正确:在 onUnload 里清理定时器
onLoad(() => {
timer = setInterval(() => {
this.updateData()
}, 1000)
})
onUnload(() => {
clearInterval(timer)
})
坑 2:在 onReady 里请求数据 ❌ → ✅
// ❌ 错误:把请求放在 onReady 里
onReady(() => {
fetchData() // 用户要等很久才能看到内容
})
// ✅ 正确:把请求放在 onLoad 里
onLoad(() => {
fetchData() // 加载和请求并行,用户更快看到内容
})
坑 3:onShow 里做太重的操作 ❌ → ✅
// ❌ 错误:在 onShow 里加载大量数据
onShow(() => {
this.loadMassiveData() // 每次切回来都加载,用户会感觉卡顿
})
// ✅ 正确:只在首次加载或数据确实需要刷新时才加载
onShow(() => {
if (this.needRefresh) {
this.loadData()
this.needRefresh = false
}
})
坑 4:onLoad 和 onShow 混淆使用 ❌ → ✅
// ❌ 错误:在 onShow 里初始化只该执行一次的东西
onShow(() => {
this.initChart() // 每次切回来都初始化,图表会重复添加
})
// ✅ 正确:只在 onLoad 里初始化一次性的东西
onLoad(() => {
this.initChart() // 只执行一次
})
onShow(() => {
this.updateChartData() // 每次显示时更新数据
})
坑 5:异步请求在 onUnload 后才返回 ❌ → ✅
// ❌ 错误:页面卸载后还在更新数据,导致报错
onLoad(async () => {
const res = await request('/api/data')
this.data = res.data // 如果用户快速返回,页面已卸载,会报错
})
// ✅ 正确:使用标志位或 onUnload 取消
onLoad(async () => {
this.pageAlive = true
const res = await request('/api/data')
if (this.pageAlive) {
this.data = res.data
}
})
onUnload(() => {
this.pageAlive = false
})
性能小贴士:懒加载数据
// ✅ 性能优化:onShow 时只刷新必要数据,不重新渲染整个页面
onShow(() => {
// 只更新变化的部分,不触发整个页面重新渲染
refreshPartialData()
})
调试技巧:生命周期日志
// 在关键生命周期里加日志,方便定位问题
onLoad((options) => {
console.log('📦 onLoad 触发,参数:', options)
})
onShow(() => {
console.log('👀 onShow 触发')
})
onReady(() => {
console.log('✅ onReady 触发,页面渲染完成')
})
onUnload(() => {
console.log('🗑️ onUnload 触发,清理资源')
})
注意!:发布前记得删掉这些 console.log,或者用条件编译去掉。
✏️ 练习题 + 作业题
练习题(5 道,10 分钟内完成)
练习 1(2 分钟):在生命周期里加日志
- 输入:在 demo1 页面的 onLoad 函数里添加 console.log('Hello from onLoad')
- 预期输出:打开页面时控制台打印 "Hello from onLoad"
- 提示:直接在 onLoad 回调里加一行 console.log 就行
练习 2(2 分钟):区分 onLoad 和 onShow
- 输入:在 onLoad 和 onShow 里分别打印不同内容,快速切到其他页面再切回来
- 预期输出:onLoad 只打印一次,onShow 打印多次
- 提示:观察控制台输出的次数差异
练习 3(2 分钟):在详情页显示传入的参数
- 输入:从列表页跳转到详情页时传 ?name=苹果&price=3
- 预期输出:详情页显示「苹果 - ¥3」
- 提示:在 onLoad 的 options 里取 name 和 price
练习 4(2 分钟):修复定时器泄漏
- 输入:有一段代码在 onLoad 里创建定时器,但没有清理
- 预期输出:添加 onUnload 清理定时器的代码
- 提示:onUnload 里调用 clearInterval
练习 5(2 分钟):给待办清单加统计
- 输入:在 todo 项目里添加「已完成 X/Y 项」的显示
- 预期输出:完成任务后,统计数字自动更新
- 提示:用 computed 计算已完成数量
作业题(30 分钟 - 2 小时)
作业:做一个「页面生命周期实战工具」
- 需求描述:做一个「学习进度追踪器」,帮助用户记录每天学习了多少分钟
- 功能点:
1. 显示今日学习时长(分钟)
2. 点击「开始学习」计时,点击「结束学习」停止
3. 数据保存在本地,刷新页面不丢失
4. 记录学习历史(最近 7 天) - 加分项:
1. 页面切换到后台时自动暂停计时
2. 用onShow检测页面是否从后台恢复,如果是则提示用户 - 验收标准:
- 能跑起来,点击按钮能正常计时
- 数据刷新后不丢失
- 代码有适当的注释
- 提交方式:评论区贴代码或 GitHub 链接
📚 总结 + 资源
本文学到的 3 个核心点
- 页面生命周期是「时间轴」:onLoad → onShow → onReady → onUnload,在正确的时间做正确的事
- onLoad 只执行一次,适合初始化;onShow 每次可见都执行,适合刷新
- onUnload 必须清理资源:定时器、事件监听、动画,否则内存泄漏
推荐延伸学习资源
互动钩子
💬 你在开发中遇到过「页面切换后数据没了」的坑吗?或者「定时器太多页面变卡」的情况?评论区聊聊,老粉优先回复!
📌 下章预告:学会了生命周期,你已经知道「什么时候该干什么」。但如果页面 A 想告诉页面 B 一件事,或者让页面 B 刷新一下,该怎么办?下一章「页面通讯:url 传参与事件总线」,教你页面之间的「对话术」!

评论(0)