一、生命周期钩子
| 生命周期钩子 | 触发时机 | 实际开发中的常见用途与举例 |
|---|---|---|
onLoad |
页面加载时触发(仅触发一次) | 接收参数与发起请求。 例如:从列表页跳转到详情页,在这里接收传递过来的商品 ID,然后请求接口获取商品数据。 |
onShow |
页面每次显示/切入前台时触发 | 刷新数据。 例如:在"购物车"页面修改了商品数量,返回"我的订单"页面时,在这里重新请求接口刷新列表。 |
onReady |
页面初次渲染完成时触发 | 操作 DOM 节点。 例如:页面加载完成后,获取某个按钮的宽高,或者初始化一个图表组件,必须在这个钩子里进行。 |
onHide |
页面隐藏/切入后台时触发 | 暂停任务。 例如:视频播放页,当用户跳转到其他页面或切到后台时,在这里暂停视频播放,避免后台消耗流量。 |
onUnload |
页面卸载时触发 | 清理资源。 例如:页面里有一个倒计时器(setInterval),当用户离开这个页面时,必须在这里清除定时器,防止内存泄漏。 |
onPullDownRefresh |
页面下拉刷新时触发 | 下拉刷新。 例如:在新闻列表页,用户手指往下拉,触发这个钩子去请求最新的新闻数据,加载完后记得调用 uni.stopPullDownRefresh() 停止刷新动画。 |
onReachBottom |
页面上拉触底时触发 | 上拉加载更多。 例如:在商品列表滑到最底部时,触发这个钩子去请求下一页的数据,并拼接到现有的列表里。 |
onPageScroll |
页面滚动时触发 | 监听滚动位置。 例如:当页面向下滚动超过 500px 时,在右下角显示一个"回到顶部"的悬浮按钮。 |
onResize |
窗口尺寸改变时触发 | 响应式布局调整。 例如:在折叠屏手机上,当屏幕折叠/展开导致宽度变化时,重新计算页面元素的排列方式。 |
onTabItemTap |
点击当前 TabBar 项时触发 | Tab 点击特殊处理。 例如:用户点击底部的"首页"Tab 时,如果当前已经在首页了,可以让页面自动滚动到最顶部。 |
onShareAppMessage |
用户点击右上角"分享"时触发 | 自定义分享内容。 例如:用户分享一篇美食文章到微信群时,在这里自定义分享的标题、封面图和跳转路径。 |
onShareTimeline |
用户点击右上角"分享到朋友圈"时触发 | 自定义朋友圈分享内容。 例如:自定义分享到朋友圈的标题、查询参数和自定义图片。 |
onAddToFavorites |
用户点击右上角"收藏"时触发 | 自定义收藏内容。 例如:自定义收藏的标题、查询参数和自定义图片,方便用户后续快速访问。 |
onBackPress |
监听页面返回(点击返回键等)时触发 | 拦截返回操作。 例如:在表单填写页,如果用户未保存内容就点击返回,可以拦截并弹窗提示"是否放弃编辑?" |
onNavigationBarButtonTap |
监听原生标题栏按钮点击时触发 | 处理导航栏自定义按钮。 例如:在 pages.json 中配置了右侧的"搜索"图标按钮,点击时在这里触发搜索框的显示。 |
onNavigationBarSearchInputChanged |
原生标题栏搜索框输入内容变化时触发 | 实时搜索提示。 例如:用户在原生搜索框打字时,在这里监听输入内容,并请求接口展示下拉联想词。 |
onNavigationBarSearchInputConfirmed |
用户点击软键盘上的"搜索"按钮时触发 | 执行搜索动作。 例如:用户输入完关键词并按下回车/搜索键,在这里获取最终文本并跳转到搜索结果页。 |
onNavigationBarSearchInputClicked |
原生标题栏搜索输入框被点击时触发 | 唤起搜索面板。 例如:点击搜索框时,不直接在标题栏输入,而是跳转到一个专门的搜索页面。 |
二、生命周期图

三、避坑指南
1. ⚠️ 生命周期使用错位(重灾区)
坑点现象:
- 在
onLoad里操作 DOM 节点,报错找不到元素。 - 在
onShow里接收页面传参,发现拿不到数据。 - 在
onUnload里发起新的网络请求,导致页面白屏或报错。
避坑指南:
- 需要获取页面宽高、初始化地图/Canvas:必须放在
onReady中,因为此时页面才真正渲染完毕。 - 接收路由参数:必须放在
onLoad中,因为onShow是没有参数对象的。 - 清理资源:
onUnload只用来清理垃圾(如解绑事件、关定时器),绝对不要在这里写业务逻辑或发网络请求。
2. ⚠️ 内存泄漏(隐形杀手)
坑点现象:
页面跳转几次后,App 变得非常卡顿,甚至崩溃。
避坑指南:
- 定时器未清理 :如果你在
onShow或onLoad里开启了setInterval,一定要在onUnload(普通页面)或onHide(TabBar 页面)中用clearInterval关掉它。 - 全局事件未解绑 :使用了
uni.$on监听事件,必须在onUnload中调用uni.$off解绑,否则事件会一直残留在内存中。 - 长连接未关闭 :WebSocket 连接在页面退出时(
onUnload)必须主动关闭。
js
// 定时器清理示例(普通页面在 onUnload 中清理)
let timer = null
onShow(() => {
timer = setInterval(() => {
// 轮询逻辑
}, 1000)
})
onUnload(() => {
if (timer) {
clearInterval(timer)
timer = null
}
})
3. ⚠️ Vue3 组合式 API 的 onShow 重复触发
坑点现象:
页面刚加载时,发现接口被请求了两次;或者从其他页面返回时,数据被意外刷新。
底层原因:
小程序是双线程架构,页面初始化时 onShow 会先于 onMounted 执行。如果你同时在 onMounted 和 onShow 里写了请求数据的代码,首次加载就会触发两次。
js
// ❌ 反面示例:首次加载会触发两次请求
onShow(() => {
fetchData()
})
onMounted(() => {
fetchData()
})
避坑指南:
- 首次加载的数据请求,统一放在
onLoad或onMounted中。 - 只有"从其他页面返回需要刷新"的场景,才把请求放在
onShow中。不要两边都写。
js
// ✅ 推荐:首次加载放 onMounted,返回刷新放 onShow
onMounted(() => {
fetchData()
})
onShow(() => {
if (needRefresh) {
refreshData()
}
})
四、uni-app 页面加载完整时序
1. 框架创建页面与原生导航栏
- 底层动作 :uni-app 框架首先读取
pages.json配置,创建页面。 - 直观表现:原生导航栏(标题栏)是最快显示出来的。
- 开发建议:页面的背景色应该在这个阶段配置,这样用户一进来就能看到底色,减少突兀感。
2. 创建首批静态 DOM
- 底层动作 :根据页面
template里的代码,创建第一批虚拟 DOM。 - 关键细节 :这一步只包含静态 DOM。那些需要通过 JS 请求数据后再通过
v-for渲染的列表,此时还不存在。 - ⚠️ 性能警告:如果页面的静态 DOM 元素过多,会严重拖慢加载速度,甚至在 Android 端(uni-app x)会阻碍页面的转场动画启动。
3. 触发 onLoad 生命周期
- 当前状态:页面还未显示,转场动画还没开始,真实的页面 DOM 还不存在。
- 能做什么 :接收上一页传来的参数(
options)、发起网络请求(uni.request)、更新data数据。 - 不能做什么:绝对不能在这里操作 DOM。
- ⚠️ 避坑指南 :不要在
onLoad里写大量耗时的同步计算。因为此时动画还没开始,同步计算会卡住 UI 线程,导致页面转场动画无法启动。
4. 转场动画开始
- 底层动作:新页面开始执行进入屏幕的过渡动画(默认耗时约 300ms)。
- 并行处理 :此时,你在
onLoad中发起的联网请求正在子线程异步获取数据,不会干扰 UI 线程的动画。
5. 触发 onReady 生命周期
- 底层动作 :虚拟 DOM 创建后,UI 层完成了真实元素的渲染,触发
onReady。 - 能做什么 :此时 DOM 已经准备就绪,你可以安全地使用
ref获取节点、计算宽高、初始化地图/Canvas 等组件。 - ⚠️ 核心重点(白屏问题) :
onReady与转场动画的先后顺序没有必然联系,完全取决于 DOM 的复杂度和渲染速度。- 渲染快:动画刚开始,内容就出来了。
- 渲染慢:动画走完了,内容还没出来,就会导致白屏。
6. 转场动画结束
- 底层动作:页面完全展示在用户面前。
- ⚠️ 核心重点 :再次强调,
onReady和"转场动画结束"谁先谁后是不一定的。如果服务器响应够快,数据甚至可能在动画结束前就渲染完毕了。
五、页面加载避坑指南
1. 页面白屏(Blank Screen)避坑指南
底层原因:
页面首批静态 DOM 飞快渲染完毕,但整个页面全是 v-for 动态列表,此时网络请求还没返回,导致页面无内容支撑,高度塌陷,视觉上表现为白屏。
避坑策略:
- 占位兜底(防塌陷) :在
template中预先写好简单的占位组件(如 loading 动画或骨架屏)。让本地先显示一些内容,等数据返回后再无缝替换,彻底消除白屏和布局抖动。 - 尽早联网 :网络请求必须放在
onLoad中尽早发起。千万不要等到onReady里再请求,那样数据渲染会慢半拍,极大增加白屏风险。 - 配置原生底色 :在
pages.json里配置原生导航栏和页面背景色。这样在页面 DOM 还没渲染出来时,用户至少能看到底色,减少突兀感。
2. 转场动画卡住/不启动 避坑指南
底层原因:
在 uni-app x (Android) 等平台上,onLoad 里的代码默认在 UI 线程运行。如果此时执行了耗时的同步计算,或者页面 DOM 元素过多,会直接阻塞 UI 线程,导致转场动画无法启动。
避坑策略:
- 控制 DOM 数量:保持页面首屏静态 DOM 的轻量化。注意排查写得过于臃肿的自定义组件,它们会严重拖累整体页面性能。
- 严禁同步耗时计算 :
onLoad里只适合做两件事:接收参数和异步联网。绝对不要在里面写复杂的循环、大数组的排序过滤等耗时同步运算。
3. 长列表渲染卡顿 避坑指南
底层原因:
v-for 是 DOM 元素的放大器。如果一次性渲染成百上千条数据,或者每个列表项(list-item)里嵌套了过多的子组件(比如复杂的评星组件),会导致 VNode 和原生 View 数量爆炸,直接卡死页面。
避坑策略:
- 使用虚拟列表 :对于长列表,务必使用
uni-list结合虚拟列表技术(仅渲染可视区域内容),或使用list-view/waterflow组件,让系统自动回收和复用渲染资源。 - 精简列表项 DOM :尽量简化
list-item内部的组件层级。例如,评星功能不要用 5 个view拼凑,可以直接用一个text组件配合字体图标(font-icon)来实现,极大减少 DOM 数量。
六、onShow和onHide
1. onShow 触发时机
- 首次进入页面:页面刚加载完显示出来时。
- 从子页面返回 :比如从"商品详情页"点击左上角返回"商品列表页",列表页会触发
onShow。 - App 切回前台 :用户按了 Home 键去回个微信,然后再切回你的 App,当前页面会触发
onShow。 - TabBar 切换 :在底部导航栏从"首页"切到"我的","我的"页面会触发
onShow。
2. onHide 触发时机
- 跳转到下一页 :从"列表页"点击跳转到"详情页",列表页会被压入后台,触发
onHide。 - App 切入后台 :用户按 Home 键或锁屏,当前页面触发
onHide。 - TabBar 切换 :从"首页"切到"我的","首页"会触发
onHide。
3. 对比表
| 对比维度 | onShow (页面显示) | onHide (页面隐藏) |
|---|---|---|
| 触发时机 | 页面每次显示/切入前台时触发 | 页面每次隐藏/切入后台时触发 |
| 触发频率 | 多次(只要页面一亮起就触发) | 多次(只要页面一隐藏就触发) |
| 能否操作DOM | ❌ 不建议(此时DOM可能还在渲染中) | ❌ 不建议(页面正在离开,操作无意义) |
| 核心作用 | 刷新数据、检查权限、重启任务等 | 暂停视频、保存草稿、清理资源等 |
4. 避坑指南
黄金搭档:必须成对使用
onShow 和 onHide 就像开关,必须成对出现。如果你在 onShow 里开启了定时器或 WebSocket,必须在 onHide 里暂停或清理它们,否则会导致严重的内存泄漏和性能问题。
js
// 示例:播放视频或倒计时
onShow(() => {
startTimer(); // 页面显示时,开始计时
});
onHide(() => {
stopTimer(); // 页面隐藏时,暂停计时
});
区分 onShow 和 onLoad
- 需要接收路由参数(如
id=123),或者只需要执行一次的初始化逻辑,放在onLoad。 - 每次页面显示都需要重新拉取最新数据的逻辑(如刷新购物车数量、检查登录状态),放在
onShow。
⚠️ TabBar 页面的特殊清理机制
这是一个极易被忽略的坑:TabBar 页面是常驻内存的,永远不会执行 onUnload!因此,TabBar 页面不能依赖 onUnload 去清理定时器或事件监听。TabBar 页面的资源释放,必须写在 onHide 中。
组合式 API (Vue3) 的命名变化
在 Vue3 的 <script setup> 中,为了防止和 App.vue 里的应用级 onShow/onHide 重名,页面级的生命周期改名为 onPageShow 和 onPageHide,使用时记得从 @dcloudio/uni-app 中导入。
七、组件的生命周期
在 uni-app 中,组件的生命周期与 Vue 的标准组件生命周期是完全一致的,它不包含 onLoad、onShow 等页面级的生命周期函数。
| 生命周期钩子 | 触发时机 | 实际开发场景 |
|---|---|---|
setup() |
组件初始化最早执行(在 beforeCreate 之前) |
定义响应式数据(ref/reactive)、声明方法、配置监听器。相当于 Vue2 的 created。 |
onBeforeMount |
模板编译完成,但组件还未挂载到真实 DOM 上 | 极少使用,偶尔用于在挂载前做最后一次数据修改。 |
onMounted |
组件挂载到 DOM 完成后 | 高频使用。发起组件内部的接口请求、安全地操作 DOM 节点、初始化第三方插件。 |
onBeforeUpdate |
组件数据更新,DOM 重新渲染之前 | 适合在更新前访问现有的 DOM,比如手动移除之前添加的事件监听器。 |
onUpdated |
数据更改导致 DOM 重新渲染和打补丁之后 | 更新后的 DOM 二次操作。注意:不要在此处修改状态,否则可能导致无限循环更新。 |
onBeforeUnmount |
组件实例被卸载之前 | 高频使用。清理垃圾的最佳时机,如清除定时器、解绑全局事件、断开 WebSocket。 |
onUnmounted |
组件卸载完成后 | 组件已被销毁,数据和方法均不可访问。 |
onActivated |
被 <keep-alive> 缓存的组件激活时 |
配合 <keep-alive> 使用,组件从缓存中恢复时触发。 |
onDeactivated |
被 <keep-alive> 缓存的组件停用时 |
配合 <keep-alive> 使用,组件被缓存隐藏时触发。 |
onErrorCaptured |
捕获来自子孙组件的异常时 | 用于集中处理子组件抛出的错误,防止整个页面崩溃。 |
实际开发中,最常用的组合是 onMounted + onBeforeUnmount:在挂载完成后发起请求、初始化资源,在卸载前统一清理定时器、事件监听或 WebSocket。
vue
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue'
const list = ref([])
let timer = null
onMounted(async () => {
list.value = await fetchList()
timer = setInterval(() => {
// 轮询逻辑
}, 1000)
})
onBeforeUnmount(() => {
if (timer) {
clearInterval(timer)
timer = null
}
})
</script>