📌 本文定位: 面向同时具备 iOS、Android、鸿蒙原生开发经验,正在转向 uni-app 的资深工程师。所有技术点均基于 uni-app 官方文档 及 Vue3 官方文档整理。
一、先搞懂一个 JS 语法问题:为什么说"与 data/methods 平级的叫生命周期"?
很多原生工程师初学 uni-app 时会对这句话感到困惑。我们从 JavaScript 语言层面彻底讲清楚。
1.1 Options API 的本质是一个"配置对象"
在 Vue2/Vue3 的 Options API 中,export default {} 导出的是一个普通 JS 对象。这个对象的每个 key 都有特定语义:
1export default { 2 // ✅ 数据属性 → Vue 会将其转为响应式 3 data() { 4 return { count: 0 } 5 }, 6 7 // ✅ 方法属性 → Vue 会将其绑定到组件实例上 8 methods: { 9 increment() { this.count++ } 10 }, 11 12 // ✅ 计算属性 → Vue 会缓存其返回值 13 computed: { 14 doubleCount() { return this.count * 2 } 15 }, 16 17 // ✅ 生命周期钩子 → Vue 会在特定时机自动调用 18 onLoad(options) { 19 console.log('页面加载', options) 20 }, 21 22 onShow() { 23 console.log('页面显示') 24 }, 25 26 mounted() { 27 console.log('DOM 挂载完成') 28 } 29} 30
1.2 Vue 内部是如何区分它们的?
Vue 在创建组件实例时,会遍历这个配置对象的所有 key,根据 key 的名称做不同的处理:
| key 名称 | Vue 内部处理方式 | 归类 |
|---|---|---|
| data | 执行函数,将返回值做响应式代理(reactive()) | 数据层 |
| methods | 遍历对象,将每个方法绑定 this 并挂载到实例 | 方法层 |
| computed | 创建惰性求值的响应式引用(computed()) | 计算层 |
| watch | 注册侦听器(watch()) | 监听层 |
| onLoad / onShow / mounted 等 | 注册为生命周期回调,在对应时机由框架自动调用 | 生命周期层 |
| 其他自定义 key | 直接挂载到实例上,不做特殊处理 | 自定义属性 |
💡 核心结论: "生命周期"不是一个 JS 语言概念,而是 Vue/uni-app 框架的约定。框架通过识别特定的 key 名称,将其从普通属性中"挑出来",注册到内部的生命周期调度系统中。这就是为什么它必须与
data、methods"平级"——因为它们都是同一个配置对象的顶层 key。
1.3 对比原生开发中的类似模式
| 平台 | 类似机制 | 说明 |
|---|---|---|
| iOS | UIViewController 的 viewDidLoad()、viewWillAppear() 等 | 系统通过方法名识别生命周期,开发者重写即可 |
| Android | Activity 的 onCreate()、onResume() 等 | 框架通过方法签名识别,自动在对应时机回调 |
| 鸿蒙 | @Component 的 aboutToAppear()、onPageShow() 等 | ArkUI 框架通过装饰器+方法名识别 |
| Vue3 Composition API | onMounted()、onUnmounted() 等组合式函数 | 不再依赖对象 key 名,改为显式函数调用注册 |
| uni-app Options API | onLoad()、onShow() 等作为配置对象顶层 key | 框架通过 key 名识别并注册 |
二、应用生命周期详解(App.vue)
应用生命周期是全局级别的,只能在 App.vue 中监听。它管理的是整个 App 进程从创建到销毁的全过程。
2.1 完整生命周期流程图
1App 启动 2 │ 3 ▼ 4┌──────────┐ ┌──────────┐ ┌──────────┐ 5│ onLaunch │────►│ onShow │◄───►│ onHide │ 6│ (仅一次) │ │(每次前台) │ │(每次后台) │ 7└──────────┘ └──────────┘ └──────────┘ 8 │ 9 用户关闭App 10 │ 11 ▼ 12 ┌──────────────┐ 13 │ onExit │ 14 │ (仅Android) │ 15 └──────────────┘ 16 17异常分支: 18 ├─ JS/API错误 ──► onError() 19 ├─ 页面不存在 ──► onPageNotFound() 20 └─ 主题切换 ──► onThemeChange() 21
2.2 逐个详解 + 四端对照
onLaunch(options)
- 触发时机: App 初始化完成时触发,全局仅一次
- 参数:
options包含启动路径path、场景值scene、查询参数query等 - 典型用途: SDK 初始化、登录态检查、全局数据预加载
1// App.vue 2export default { 3 onLaunch(options) { 4 console.log('App Launch, 启动参数:', options); 5 6 // 初始化推送 SDK 7 // #ifdef APP-PLUS 8 plus.push.addEventListener('click', (msg) => { 9 console.log('推送点击:', msg); 10 }); 11 // #endif 12 13 // 检查登录态 14 const token = uni.getStorageSync('token'); 15 if (!token) { 16 uni.reLaunch({ url: '/pages/login/login' }); 17 } 18 } 19} 20
onShow(options)
- 触发时机: App 启动 或 从后台切回前台时触发
- 参数: 同
onLaunch - 典型用途: 刷新数据、恢复任务、版本检查
1export default { 2 onShow(options) { 3 console.log('App Show, 场景值:', options.scene); 4 // 每次回到前台都检查是否有新消息 5 this.checkNewMessages(); 6 } 7} 8
onHide()
- 触发时机: App 从前台进入后台时触发
- 典型用途: 暂停任务、保存状态、释放资源
1export default { 2 onHide() { 3 console.log('App Hide'); 4 // 保存播放进度 5 uni.setStorageSync('music_progress', this.currentProgress); 6 } 7} 8
onError(err)
- 触发时机: JS 执行错误或 API 调用失败时
- 典型用途: 全局错误上报、崩溃监控
1export default { 2 onError(err) { 3 console.error('全局错误:', err); 4 // 上报到 Sentry / Bugly 5 // reportError(err); 6 } 7} 8
onPageNotFound(res)
- 触发时机: 打开不存在的页面时(如分享链接指向已删除页面)
- 参数:
res.path(不存在的路径)、res.query - 典型用途: 降级跳转到首页或404页
1export default { 2 onPageNotFound(res) { 3 console.warn('页面不存在:', res.path); 4 uni.redirectTo({ url: '/pages/index/index' }); 5 } 6} 7
onThemeChange(res)
- 触发时机: 系统深色/浅色模式切换时
- 参数:
res.theme('dark'|'light')
1export default { 2 onThemeChange(res) { 3 console.log('主题切换:', res.theme); 4 // 通知全局状态更新 5 getApp().globalData.isDarkMode = res.theme === 'dark'; 6 } 7} 8
2.3 应用生命周期四端对照表(重点收藏)
| uni-app | iOS | Android | 鸿蒙 (ArkUI) | Vue3 App 级 | 说明 |
|---|---|---|---|---|---|
| onLaunch | application(_:didFinishLaunchingWithOptions:) | Application.onCreate() | AbilityStage.onCreate() | app.onMount() (无直接对应) | 进程创建,全局仅一次 |
| onShow | applicationDidBecomeActive(_:) / sceneDidBecomeActive(_:) | Activity.onResume() / ProcessLifecycleOwner ON_START | UIAbility.onForeground() | — | 进入前台/可见 |
| onHide | applicationDidEnterBackground(_:) / sceneDidEnterBackground(_:) | Activity.onPause() / ProcessLifecycleOwner ON_STOP | UIAbility.onBackground() | — | 进入后台/不可见 |
| onExit | applicationWillTerminate(_:) | Activity.onDestroy() / Application.onTerminate() | UIAbility.onDestroy() | app.onUnmount() | 进程销毁(不一定触发) |
| onError | NSSetUncaughtExceptionHandler | Thread.UncaughtExceptionHandler | AbilityStage.onError() | app.config.errorHandler | 全局异常捕获 |
| onPageNotFound | — | — | — | router.onError() | 路由兜底 |
| onThemeChange | traitCollectionDidChange(_:) | onConfigurationChanged() | Environment.colorScheme 变化 | — | 深色模式切换 |
⚠️ 关键差异提醒:
- iOS 的
applicationWillTerminate在多任务杀进程时不一定被调用,uni-app 的onExit同理- Android 的
onDestroy也不保证触发,不要在此做关键数据持久化- 鸿蒙的
UIAbility.onForeground/onBackground是 Ability 级别,与 uni-app 的 App 级别不完全等价- Vue3 本身没有 App 级别的 show/hide 概念,这是 uni-app 针对移动端扩展的
三、页面生命周期详解(pages/*.vue)
页面生命周期是页面级别的,在每个 .vue 页面文件中监听。它管理的是单个页面从创建到销毁的过程。
3.1 完整页面生命周期流程图
1navigateTo / redirectTo / reLaunch 2 │ 3 ▼ 4 ┌──────────┐ 5 │ onLoad │ ← 接收页面参数 options 6 └────┬─────┘ 7 │ 8 ▼ 9 ┌──────────┐ 10 │ onShow │ ← 每次页面显示都触发 11 └────┬─────┘ 12 │ 13 ▼ 14 ┌─────────────┐ 15 │ onReady │ ← DOM/原生渲染完成(仅首次) 16 └──────┬──────┘ 17 │ 18 用户操作/导航 19 │ 20 ┌───────┴───────┐ 21 ▼ ▼ 22┌──────────┐ ┌────────────┐ 23│ onHide │ │ onUnload │ 24│(页面隐藏) │ │(页面销毁) │ 25└──────────┘ └────────────┘ 26 │ │ 27 ▼ ▼ 28 再次 onShow 生命周期结束 29
3.2 逐个详解 + 示例代码
onLoad(options)
- 触发时机: 页面创建时触发,每个页面实例仅一次
- 参数:
options为页面路由参数(URL query 解析后的对象) - 典型用途: 接收参数、初始化页面数据、发起首次请求
1// pages/detail/detail.vue 2export default { 3 onLoad(options) { 4 // options.id = "123" (来自 /pages/detail/detail?id=123) 5 this.productId = options.id; 6 this.loadProductDetail(options.id); 7 }, 8 data() { 9 return { productId: '', product: {} } 10 }, 11 methods: { 12 async loadProductDetail(id) { 13 const res = await getProductById(id); 14 this.product = res.data; 15 } 16 } 17} 18
onShow()
- 触发时机: 页面每次显示时触发(包括首次加载和从其他页面返回)
- 典型用途: 刷新列表、更新状态、埋点曝光
1export default { 2 onShow() { 3 // 每次回到此页面都刷新订单列表 4 this.refreshOrderList(); 5 // 埋点 6 uni.reportEvent('page_view', { page: 'order_list' }); 7 } 8} 9
onReady()
- 触发时机: 页面初次渲染完成时触发(DOM/原生视图就绪)
- 典型用途: 操作 DOM、初始化地图/视频/canvas 等需要视图就绪的组件
- 注意: 仅在首次加载时触发,
navigateBack返回不会再次触发
1export default { 2 onReady() { 3 // 地图组件需要在 ready 后才能调用方法 4 this.mapContext = uni.createMapContext('myMap', this); 5 this.mapContext.moveToLocation(); 6 } 7} 8
onHide()
- 触发时机: 页面隐藏时触发(
navigateTo到新页面、切后台、切 Tab) - 典型用途: 暂停动画/视频、取消定时器
1export default { 2 onHide() { 3 // 暂停轮播图定时器 4 clearInterval(this.swiperTimer); 5 } 6} 7
onUnload()
- 触发时机: 页面卸载时触发(
navigateBack、redirectTo、reLaunch) - 典型用途: 清理资源、移除事件监听、取消网络请求
1export default { 2 onUnload() { 3 // 清除定时器 4 clearInterval(this.timer); 5 // 移除全局事件监听 6 uni.$off('cartUpdated', this.onCartUpdated); 7 } 8} 9
其他页面生命周期
| 函数 | 触发时机 | 用途 |
|---|---|---|
| onPullDownRefresh() | 下拉刷新时 | 需 pages.json 开启 enablePullDownRefresh |
| onReachBottom() | 滚动到底部时 | 分页加载更多 |
| onShareAppMessage() | 点击右上角分享时 | 返回分享内容配置 |
| onPageScroll(obj) | 页面滚动时 | obj.scrollTop;慎用,性能敏感 |
| onResize(obj) | 窗口尺寸变化时 | 横竖屏切换、折叠屏展开 |
| onTabItemTap(obj) | 点击当前 TabBar 项时 | 仅当前 Tab 页有效 |
| onBackPress() | 返回键/手势/导航栏返回时 | 可拦截返回行为,返回 true 阻止默认返回 |
3.3 页面生命周期四端对照表
| uni-app | iOS | Android | 鸿蒙 (ArkUI) | Vue3 组件生命周期 | 说明 |
|---|---|---|---|---|---|
| onLoad | viewDidLoad() / init(coder:) | onCreate() | aboutToAppear() | onBeforeMount | 页面创建,接收参数 |
| onShow | viewWillAppear(_:) | onResume() | onPageShow() | onMounted (首次) | 页面可见 |
| onReady | viewDidAppear(_:) (首次) | onWindowFocusChanged(true) (首次) | onDidBuild() | onMounted | 视图渲染完成 |
| onHide | viewWillDisappear(_:) | onPause() | onPageHide() | onBeforeUnmount (部分) | 页面不可见 |
| onUnload | deinit / viewDidDisappear (最终) | onDestroy() | aboutToDisappear() | onUnmounted | 页面销毁 |
| onPullDownRefresh | UIRefreshControl | SwipeRefreshLayout | Refresh 组件 | — | 下拉刷新 |
| onReachBottom | scrollViewDidScroll + offset判断 | RecyclerView.OnScrollListener | List.onReachEnd | — | 触底加载 |
| onBackPress | navigationController?.popViewController 拦截 | onBackPressedDispatcher | onBackPress() | — | 返回拦截 |
| onPageScroll | scrollViewDidScroll(_:) | OnScrollListener | onScroll() | — | ⚠️ 高频触发 |
四、Vue3 生命周期 vs uni-app 生命周期:两套体系如何共存?
这是很多开发者最容易混淆的点。uni-app 中实际上存在两套生命周期系统,它们各司其职:
4.1 双轨制对照
| 类别 | Vue3 标准生命周期 | uni-app 扩展生命周期 | 职责划分 |
|---|---|---|---|
| 组件级 | onBeforeMount / onMounted / onBeforeUpdate / onUpdated / onBeforeUnmount / onUnmounted | — | 管理 DOM/虚拟节点 的创建、更新、销毁 |
| 页面级 | — | onLoad / onShow / onReady / onHide / onUnload / onPullDownRefresh / onReachBottom 等 | 管理 页面路由、导航、交互 相关逻辑 |
| 应用级 | app.onMount / app.onUnmount | onLaunch / onShow / onHide / onError 等 | 管理 App 进程 的全局状态 |
4.2 执行顺序(关键!)
当一个页面首次加载时,两套生命周期的执行顺序如下:
1onLoad (uni-app) ← 最早,接收参数 2 │ 3 ▼ 4onBeforeMount (Vue3) ← 虚拟 DOM 即将挂载 5 │ 6 ▼ 7onMounted (Vue3) ← 虚拟 DOM 挂载完成 8 │ 9 ▼ 10onReady (uni-app) ← 原生视图/真实 DOM 渲染完成 11 │ 12 ▼ 13onShow (uni-app) ← 页面可见 14
⚠️ 重要区别:
onMounted≠onReady:onMounted表示 Vue 虚拟 DOM 挂载完成,但原生视图可能还没渲染好;onReady才表示真实视图就绪- 操作 canvas/map/video 等原生组件,必须在
onReady中,而非onMountedonShow在onReady之后触发,且每次页面显示都会触发
4.3 Composition API 中的写法
如果你使用 Vue3 <script setup> 语法,两套生命周期都可以直接使用:
1<script setup> 2import { ref, onMounted, onUnmounted } from 'vue'; 3import { onLoad, onShow, onReady, onUnload } from '@dcloudio/uni-app'; 4 5const productId = ref(''); 6const product = ref({}); 7 8// ✅ uni-app 页面生命周期 9onLoad((options) => { 10 productId.value = options.id; 11 console.log('onLoad:', options); 12}); 13 14onShow(() => { 15 console.log('onShow'); 16}); 17 18onReady(() => { 19 console.log('onReady - 可以操作原生组件了'); 20}); 21 22onUnload(() => { 23 console.log('onUnload - 清理资源'); 24}); 25 26// ✅ Vue3 组件生命周期 27onMounted(() => { 28 console.log('onMounted - 虚拟DOM已挂载'); 29}); 30 31onUnmounted(() => { 32 console.log('onUnmounted - 组件卸载'); 33}); 34</script> 35
📖 注意: 在
<script setup>中使用 uni-app 页面生命周期,需要从@dcloudio/uni-app导入。这些是 uni-app 对 Vue3 的组合式函数扩展。
五、JS 架构深层理解:Options API vs Composition API 的生命周期注册机制
作为资深工程师,理解框架"怎么做到的"比"怎么用"更重要。
5.1 Options API:基于对象 key 名的约定式注册
1// 你写的代码 2export default { 3 data() { return { count: 0 } }, 4 methods: { add() { this.count++ } }, 5 onLoad(opts) { /* ... */ }, 6 onShow() { /* ... */ } 7} 8
框架内部伪代码:
1function createComponentInstance(options) { 2 const instance = {}; 3 4 for (const key in options) { 5 if (key === 'data') { 6 // 响应式处理 7 instance.data = reactive(options.data()); 8 } else if (key === 'methods') { 9 // 绑定 this 10 Object.keys(options.methods).forEach(m => { 11 instance[m] = options.methods[m].bind(instance); 12 }); 13 } else if (LIFECYCLE_HOOKS.includes(key)) { 14 // 🔑 关键:识别生命周期 key,注册到调度器 15 registerLifecycleHook(instance, key, options[key]); 16 } else { 17 // 其他属性直接挂载 18 instance[key] = options[key]; 19 } 20 } 21 return instance; 22} 23 24const LIFECYCLE_HOOKS = [ 25 'onLoad', 'onShow', 'onReady', 'onHide', 'onUnload', 26 'onPullDownRefresh', 'onReachBottom', 'onShareAppMessage', 27 'onPageScroll', 'onResize', 'onTabItemTap', 'onBackPress', 28 'mounted', 'beforeMount', 'unmounted', 'beforeUnmount' 29]; 30
5.2 Composition API:基于显式函数调用的声明式注册
1// 你写的代码 2import { onLoad, onShow } from '@dcloudio/uni-app'; 3import { onMounted } from 'vue'; 4 5onLoad((opts) => { /* ... */ }); 6onShow(() => { /* ... */ }); 7onMounted(() => { /* ... */ }); 8
框架内部伪代码:
1// @dcloudio/uni-app 中的实现 2let currentInstance = null; 3 4export function onLoad(callback) { 5 // 获取当前正在创建的组件实例 6 const instance = getCurrentInstance(); 7 // 将回调注册到实例的生命周期队列中 8 instance.lifecycleHooks.onLoad = callback; 9} 10 11export function onShow(callback) { 12 const instance = getCurrentInstance(); 13 instance.lifecycleHooks.onShow = callback; 14} 15
5.3 两种方式的本质区别
| 维度 | Options API | Composition API |
|---|---|---|
| 注册方式 | 对象 key 名约定 | 显式函数调用 |
| 识别机制 | 框架遍历 key 名匹配 | 开发者主动调用注册函数 |
| 灵活性 | 固定结构,不易拆分 | 可按逻辑关注点自由组织 |
| TypeScript 支持 | 较弱(需额外类型声明) | 天然支持(函数有明确类型) |
| 代码复用 | Mixin(命名冲突风险) | Composables(清晰无冲突) |
| 推荐场景 | 简单页面、快速原型 | 复杂业务、团队协作、长期维护 |
六、实战最佳实践与避坑指南
6.1 数据初始化放哪里?
| 场景 | 推荐位置 | 原因 |
|---|---|---|
| 依赖 URL 参数的数据 | onLoad | 只有这里能拿到 options |
| 不依赖参数的静态数据 | data() / ref() | 声明即初始化 |
| 每次进入都需要刷新的数据 | onShow | 返回时也能刷新 |
| 需要 DOM 就绪的数据 | onReady | 如地图中心点、canvas 尺寸 |
| 全局共享数据 | App.globalData / Pinia | 跨页面共享 |
6.2 常见坑点速查
| 坑点 | 现象 | 解决方案 |
|---|---|---|
| onLoad 中跳转白屏 | App 未完全初始化 | 改用 onShow 或 setTimeout 延迟跳转 |
| onMounted 中操作 map 报错 | 原生视图未就绪 | 改用 onReady |
| onShow 中拿不到最新参数 | onShow 无 options 参数 | 在 onLoad 中存到 data,onShow 中读取 |
| onPageScroll 卡顿 | 高频触发导致通信开销 | 用 CSS position: sticky 替代,或节流处理 |
| onUnload 不触发 | 使用了 switchTab 而非 navigateBack | Tab 页用 onHide 代替 |
| globalData 修改后页面不更新 | 非响应式 | 在 onShow 中重新赋值,或改用 Pinia |
| H5 端 onLoad 参数丢失 | 浏览器刷新导致 | H5 端建议用 Vue Router 的 route.query 辅助 |
6.3 生命周期选择决策树
1需要在某个时机执行代码? 2 │ 3 ├─ 与 App 进程相关? 4 │ ├─ 初始化/SDK ──► onLaunch 5 │ ├─ 前后台切换 ──► onShow / onHide 6 │ └─ 全局错误 ──► onError 7 │ 8 ├─ 与页面导航相关? 9 │ ├─ 接收参数 ──► onLoad 10 │ ├─ 每次显示刷新 ──► onShow 11 │ ├─ 返回拦截 ──► onBackPress 12 │ └─ 页面销毁清理 ──► onUnload 13 │ 14 ├─ 与视图渲染相关? 15 │ ├─ 操作原生组件 ──► onReady 16 │ ├─ DOM 操作(Vue) ──► onMounted 17 │ └─ 组件卸载 ──► onUnmounted 18 │ 19 └─ 与用户交互相关? 20 ├─ 下拉刷新 ──► onPullDownRefresh 21 ├─ 触底加载 ──► onReachBottom 22 ├─ 滚动监听 ──► onPageScroll (⚠️慎用) 23 └─ 分享 ──► onShareAppMessage 24
七、总结:四端生命周期全景对照表
| uni-app | iOS | Android | 鸿蒙 | Vue3 | 层级 |
|---|---|---|---|---|---|
| onLaunch | didFinishLaunching | Application.onCreate | AbilityStage.onCreate | — | App |
| onShow | didBecomeActive | onResume | onForeground | — | App |
| onHide | didEnterBackground | onPause | onBackground | — | App |
| onExit | willTerminate | onDestroy | onDestroy | app.onUnmount | App |
| onLoad | viewDidLoad | onCreate | aboutToAppear | onBeforeMount | Page |
| onShow (页面) | viewWillAppear | onResume | onPageShow | onMounted | Page |
| onReady | viewDidAppear | onWindowFocusChanged | onDidBuild | onMounted | Page |
| onHide (页面) | viewWillDisappear | onPause | onPageHide | — | Page |
| onUnload | deinit | onDestroy | aboutToDisappear | onUnmounted | Page |
💡 给多端原生工程师的一句话: uni-app 的生命周期设计是对 iOS/Android/鸿蒙三者最大公约数的抽象。
onLoad ≈ viewDidLoad ≈ onCreate ≈ aboutToAppear,onShow ≈ viewWillAppear ≈ onResume ≈ onPageShow。掌握了这个映射关系,你就能在四种技术栈之间自如切换。框架在变,生命周期的本质思想从未改变。
📚 参考资料:
《uni-app 生命周期深度解析(iOS / Android / 鸿蒙 / Vue3 四端对照)》 是转载文章,点击查看原文。
