七、uni-app页面与组件生命周期

一、生命周期钩子

生命周期钩子 触发时机 实际开发中的常见用途与举例
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 变得非常卡顿,甚至崩溃。

避坑指南:

  • 定时器未清理 :如果你在 onShowonLoad 里开启了 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 执行。如果你同时在 onMountedonShow 里写了请求数据的代码,首次加载就会触发两次。

js 复制代码
// ❌ 反面示例:首次加载会触发两次请求
onShow(() => {
  fetchData()
})

onMounted(() => {
  fetchData()
})

避坑指南:

  • 首次加载的数据请求,统一放在 onLoadonMounted 中。
  • 只有"从其他页面返回需要刷新"的场景,才把请求放在 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. 避坑指南

黄金搭档:必须成对使用

onShowonHide 就像开关,必须成对出现。如果你在 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 重名,页面级的生命周期改名为 onPageShowonPageHide,使用时记得从 @dcloudio/uni-app 中导入。

七、组件的生命周期

在 uni-app 中,组件的生命周期与 Vue 的标准组件生命周期是完全一致的,它不包含 onLoadonShow 等页面级的生命周期函数。

生命周期钩子 触发时机 实际开发场景
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>
相关推荐
绿岛之北1 小时前
Electron 安全第三章:URL 加载与 WebView
前端·electron
sunoo-2291 小时前
C 语言文件 IO 全攻略:从基础函数到实战踩坑(BMP 读取 + 词典查询)
linux·c语言·前端·笔记·vscode·学习
Shinner欣儿1 小时前
React18 并发渲染小记
前端
用户921080262862 小时前
AI 对话里的消息时间线分页:上滑加载更多历史记忆的实现与坑点
前端
PedroQue992 小时前
Vue-Router 2.4.0 新增可控重定向功能
前端·uni-app
阿萨德528号2 小时前
npm 包发布实战指南:从零发布、更新迭代到版本治理
前端·npm·策略模式
IT_陈寒2 小时前
Vue的v-for为啥把我的渲染顺序搞乱套了?
前端·人工智能·后端
sir.山3 小时前
在 Vue 3 项目中使用 Mock 数据
前端·vue.js·vue3·vite
李剑一3 小时前
华为新上Pura X View阔直板手机,特殊屏幕比例设备下,前端应该怎么去适配更完美?送你一套完整工具代码
前端