uni-app 生命周期深度解析(iOS / Android / 鸿蒙 / Vue3 四端对照)

36 阅读12分钟

📌 本文定位: 面向同时具备 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 都有特定语义:

export default {
  // ✅ 数据属性 → Vue 会将其转为响应式
  data() {
    return { count: 0 }
  },

  // ✅ 方法属性 → Vue 会将其绑定到组件实例上
  methods: {
    increment() { this.count++ }
  },

  // ✅ 计算属性 → Vue 会缓存其返回值
  computed: {
    doubleCount() { return this.count * 2 }
  },

  // ✅ 生命周期钩子 → Vue 会在特定时机自动调用
  onLoad(options) {
    console.log('页面加载', options)
  },

  onShow() {
    console.log('页面显示')
  },

  mounted() {
    console.log('DOM 挂载完成')
  }
}

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 名称,将其从普通属性中"挑出来",注册到内部的生命周期调度系统中。这就是为什么它必须与 datamethods "平级"——因为它们都是同一个配置对象的顶层 key。

1.3 对比原生开发中的类似模式

平台类似机制说明
iOSUIViewControllerviewDidLoad()viewWillAppear()系统通过方法名识别生命周期,开发者重写即可
AndroidActivityonCreate()onResume()框架通过方法签名识别,自动在对应时机回调
鸿蒙@ComponentaboutToAppear()onPageShow()ArkUI 框架通过装饰器+方法名识别
Vue3 Composition APIonMounted()onUnmounted() 等组合式函数不再依赖对象 key 名,改为显式函数调用注册
uni-app Options APIonLoad()onShow() 等作为配置对象顶层 key框架通过 key 名识别并注册

二、应用生命周期详解(App.vue)

应用生命周期是全局级别的,只能在 App.vue 中监听。它管理的是整个 App 进程从创建到销毁的全过程。

2.1 完整生命周期流程图

App 启动
   │
   ▼
┌──────────┐     ┌──────────┐     ┌──────────┐
│ onLaunch │────►│  onShow  │◄───►│  onHide  │
│ (仅一次)  │     │(每次前台) │     │(每次后台) │
└──────────┘     └──────────┘     └──────────┘
                      │
                 用户关闭App
                      │
                      ▼
              ┌──────────────┐
              │   onExit     │
              │ (仅Android)  │
              └──────────────┘

异常分支:
  ├─ JS/API错误 ──► onError()
  ├─ 页面不存在 ──► onPageNotFound()
  └─ 主题切换   ──► onThemeChange()

2.2 逐个详解 + 四端对照

onLaunch(options)

  • 触发时机: App 初始化完成时触发,全局仅一次
  • 参数: options 包含启动路径 path、场景值 scene、查询参数 query
  • 典型用途: SDK 初始化、登录态检查、全局数据预加载
// App.vue
export default {
  onLaunch(options) {
    console.log('App Launch, 启动参数:', options);
    
    // 初始化推送 SDK
    // #ifdef APP-PLUS
    plus.push.addEventListener('click', (msg) => {
      console.log('推送点击:', msg);
    });
    // #endif
    
    // 检查登录态
    const token = uni.getStorageSync('token');
    if (!token) {
      uni.reLaunch({ url: '/pages/login/login' });
    }
  }
}

onShow(options)

  • 触发时机: App 启动 从后台切回前台时触发
  • 参数:onLaunch
  • 典型用途: 刷新数据、恢复任务、版本检查
export default {
  onShow(options) {
    console.log('App Show, 场景值:', options.scene);
    // 每次回到前台都检查是否有新消息
    this.checkNewMessages();
  }
}

onHide()

  • 触发时机: App 从前台进入后台时触发
  • 典型用途: 暂停任务、保存状态、释放资源
export default {
  onHide() {
    console.log('App Hide');
    // 保存播放进度
    uni.setStorageSync('music_progress', this.currentProgress);
  }
}

onError(err)

  • 触发时机: JS 执行错误或 API 调用失败时
  • 典型用途: 全局错误上报、崩溃监控
export default {
  onError(err) {
    console.error('全局错误:', err);
    // 上报到 Sentry / Bugly
    // reportError(err);
  }
}

onPageNotFound(res)

  • 触发时机: 打开不存在的页面时(如分享链接指向已删除页面)
  • 参数: res.path(不存在的路径)、res.query
  • 典型用途: 降级跳转到首页或404页
export default {
  onPageNotFound(res) {
    console.warn('页面不存在:', res.path);
    uni.redirectTo({ url: '/pages/index/index' });
  }
}

onThemeChange(res)

  • 触发时机: 系统深色/浅色模式切换时
  • 参数: res.theme'dark' | 'light'
export default {
  onThemeChange(res) {
    console.log('主题切换:', res.theme);
    // 通知全局状态更新
    getApp().globalData.isDarkMode = res.theme === 'dark';
  }
}

2.3 应用生命周期四端对照表(重点收藏)

uni-appiOSAndroid鸿蒙 (ArkUI)Vue3 App 级说明
onLaunchapplication(_:didFinishLaunchingWithOptions:)Application.onCreate()AbilityStage.onCreate()app.onMount() (无直接对应)进程创建,全局仅一次
onShowapplicationDidBecomeActive(_:) / sceneDidBecomeActive(_:)Activity.onResume() / ProcessLifecycleOwner ON_STARTUIAbility.onForeground()进入前台/可见
onHideapplicationDidEnterBackground(_:) / sceneDidEnterBackground(_:)Activity.onPause() / ProcessLifecycleOwner ON_STOPUIAbility.onBackground()进入后台/不可见
onExitapplicationWillTerminate(_:)Activity.onDestroy() / Application.onTerminate()UIAbility.onDestroy()app.onUnmount()进程销毁(不一定触发)
onErrorNSSetUncaughtExceptionHandlerThread.UncaughtExceptionHandlerAbilityStage.onError()app.config.errorHandler全局异常捕获
onPageNotFoundrouter.onError()路由兜底
onThemeChangetraitCollectionDidChange(_:)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 完整页面生命周期流程图

navigateTo / redirectTo / reLaunch
              │
              ▼
        ┌──────────┐
        │  onLoad  │ ← 接收页面参数 options
        └────┬─────┘
             │
             ▼
        ┌──────────┐
        │ onShow   │ ← 每次页面显示都触发
        └────┬─────┘
             │
             ▼
      ┌─────────────┐
      │  onReady    │ ← DOM/原生渲染完成(仅首次)
      └──────┬──────┘
             │
        用户操作/导航
             │
     ┌───────┴───────┐
     ▼               ▼
┌──────────┐   ┌────────────┐
│  onHide  │   │ onUnload   │
│(页面隐藏) │   │(页面销毁)   │
└──────────┘   └────────────┘
     │               │
     ▼               ▼
  再次 onShow     生命周期结束

3.2 逐个详解 + 示例代码

onLoad(options)

  • 触发时机: 页面创建时触发,每个页面实例仅一次
  • 参数: options 为页面路由参数(URL query 解析后的对象)
  • 典型用途: 接收参数、初始化页面数据、发起首次请求
// pages/detail/detail.vue
export default {
  onLoad(options) {
    // options.id = "123" (来自 /pages/detail/detail?id=123)
    this.productId = options.id;
    this.loadProductDetail(options.id);
  },
  data() {
    return { productId: '', product: {} }
  },
  methods: {
    async loadProductDetail(id) {
      const res = await getProductById(id);
      this.product = res.data;
    }
  }
}

onShow()

  • 触发时机: 页面每次显示时触发(包括首次加载和从其他页面返回)
  • 典型用途: 刷新列表、更新状态、埋点曝光
export default {
  onShow() {
    // 每次回到此页面都刷新订单列表
    this.refreshOrderList();
    // 埋点
    uni.reportEvent('page_view', { page: 'order_list' });
  }
}

onReady()

  • 触发时机: 页面初次渲染完成时触发(DOM/原生视图就绪)
  • 典型用途: 操作 DOM、初始化地图/视频/canvas 等需要视图就绪的组件
  • 注意: 仅在首次加载时触发,navigateBack 返回不会再次触发
export default {
  onReady() {
    // 地图组件需要在 ready 后才能调用方法
    this.mapContext = uni.createMapContext('myMap', this);
    this.mapContext.moveToLocation();
  }
}

onHide()

  • 触发时机: 页面隐藏时触发(navigateTo 到新页面、切后台、切 Tab)
  • 典型用途: 暂停动画/视频、取消定时器
export default {
  onHide() {
    // 暂停轮播图定时器
    clearInterval(this.swiperTimer);
  }
}

onUnload()

  • 触发时机: 页面卸载时触发(navigateBackredirectToreLaunch
  • 典型用途: 清理资源、移除事件监听、取消网络请求
export default {
  onUnload() {
    // 清除定时器
    clearInterval(this.timer);
    // 移除全局事件监听
    uni.$off('cartUpdated', this.onCartUpdated);
  }
}

其他页面生命周期

函数触发时机用途
onPullDownRefresh()下拉刷新时pages.json 开启 enablePullDownRefresh
onReachBottom()滚动到底部时分页加载更多
onShareAppMessage()点击右上角分享时返回分享内容配置
onPageScroll(obj)页面滚动时obj.scrollTop慎用,性能敏感
onResize(obj)窗口尺寸变化时横竖屏切换、折叠屏展开
onTabItemTap(obj)点击当前 TabBar 项时仅当前 Tab 页有效
onBackPress()返回键/手势/导航栏返回时可拦截返回行为,返回 true 阻止默认返回

3.3 页面生命周期四端对照表

uni-appiOSAndroid鸿蒙 (ArkUI)Vue3 组件生命周期说明
onLoadviewDidLoad() / init(coder:)onCreate()aboutToAppear()onBeforeMount页面创建,接收参数
onShowviewWillAppear(_:)onResume()onPageShow()onMounted (首次)页面可见
onReadyviewDidAppear(_:) (首次)onWindowFocusChanged(true) (首次)onDidBuild()onMounted视图渲染完成
onHideviewWillDisappear(_:)onPause()onPageHide()onBeforeUnmount (部分)页面不可见
onUnloaddeinit / viewDidDisappear (最终)onDestroy()aboutToDisappear()onUnmounted页面销毁
onPullDownRefreshUIRefreshControlSwipeRefreshLayoutRefresh 组件下拉刷新
onReachBottomscrollViewDidScroll + offset判断RecyclerView.OnScrollListenerList.onReachEnd触底加载
onBackPressnavigationController?.popViewController 拦截onBackPressedDispatcheronBackPress()返回拦截
onPageScrollscrollViewDidScroll(_:)OnScrollListeneronScroll()⚠️ 高频触发

四、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.onUnmountonLaunch / onShow / onHide / onError管理 App 进程 的全局状态

4.2 执行顺序(关键!)

当一个页面首次加载时,两套生命周期的执行顺序如下:

onLoad (uni-app)          ← 最早,接收参数
    │
    ▼
onBeforeMount (Vue3)     ← 虚拟 DOM 即将挂载
    │
    ▼
onMounted (Vue3)         ← 虚拟 DOM 挂载完成
    │
    ▼
onReady (uni-app)        ← 原生视图/真实 DOM 渲染完成
    │
    ▼
onShow (uni-app)         ← 页面可见

⚠️ 重要区别:

  • onMountedonReadyonMounted 表示 Vue 虚拟 DOM 挂载完成,但原生视图可能还没渲染好;onReady 才表示真实视图就绪
  • 操作 canvas/map/video 等原生组件,必须在 onReady,而非 onMounted
  • onShowonReady 之后触发,且每次页面显示都会触发

4.3 Composition API 中的写法

如果你使用 Vue3 <script setup> 语法,两套生命周期都可以直接使用:

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { onLoad, onShow, onReady, onUnload } from '@dcloudio/uni-app';

const productId = ref('');
const product = ref({});

// ✅ uni-app 页面生命周期
onLoad((options) => {
  productId.value = options.id;
  console.log('onLoad:', options);
});

onShow(() => {
  console.log('onShow');
});

onReady(() => {
  console.log('onReady - 可以操作原生组件了');
});

onUnload(() => {
  console.log('onUnload - 清理资源');
});

// ✅ Vue3 组件生命周期
onMounted(() => {
  console.log('onMounted - 虚拟DOM已挂载');
});

onUnmounted(() => {
  console.log('onUnmounted - 组件卸载');
});
</script>

📖 注意:<script setup> 中使用 uni-app 页面生命周期,需要从 @dcloudio/uni-app 导入。这些是 uni-app 对 Vue3 的组合式函数扩展。


五、JS 架构深层理解:Options API vs Composition API 的生命周期注册机制

作为资深工程师,理解框架"怎么做到的"比"怎么用"更重要。

5.1 Options API:基于对象 key 名的约定式注册

// 你写的代码
export default {
  data() { return { count: 0 } },
  methods: { add() { this.count++ } },
  onLoad(opts) { /* ... */ },
  onShow() { /* ... */ }
}

框架内部伪代码:

function createComponentInstance(options) {
  const instance = {};
  
  for (const key in options) {
    if (key === 'data') {
      // 响应式处理
      instance.data = reactive(options.data());
    } else if (key === 'methods') {
      // 绑定 this
      Object.keys(options.methods).forEach(m => {
        instance[m] = options.methods[m].bind(instance);
      });
    } else if (LIFECYCLE_HOOKS.includes(key)) {
      // 🔑 关键:识别生命周期 key,注册到调度器
      registerLifecycleHook(instance, key, options[key]);
    } else {
      // 其他属性直接挂载
      instance[key] = options[key];
    }
  }
  return instance;
}

const LIFECYCLE_HOOKS = [
  'onLoad', 'onShow', 'onReady', 'onHide', 'onUnload',
  'onPullDownRefresh', 'onReachBottom', 'onShareAppMessage',
  'onPageScroll', 'onResize', 'onTabItemTap', 'onBackPress',
  'mounted', 'beforeMount', 'unmounted', 'beforeUnmount'
];

5.2 Composition API:基于显式函数调用的声明式注册

// 你写的代码
import { onLoad, onShow } from '@dcloudio/uni-app';
import { onMounted } from 'vue';

onLoad((opts) => { /* ... */ });
onShow(() => { /* ... */ });
onMounted(() => { /* ... */ });

框架内部伪代码:

// @dcloudio/uni-app 中的实现
let currentInstance = null;

export function onLoad(callback) {
  // 获取当前正在创建的组件实例
  const instance = getCurrentInstance();
  // 将回调注册到实例的生命周期队列中
  instance.lifecycleHooks.onLoad = callback;
}

export function onShow(callback) {
  const instance = getCurrentInstance();
  instance.lifecycleHooks.onShow = callback;
}

5.3 两种方式的本质区别

维度Options APIComposition API
注册方式对象 key 名约定显式函数调用
识别机制框架遍历 key 名匹配开发者主动调用注册函数
灵活性固定结构,不易拆分可按逻辑关注点自由组织
TypeScript 支持较弱(需额外类型声明)天然支持(函数有明确类型)
代码复用Mixin(命名冲突风险)Composables(清晰无冲突)
推荐场景简单页面、快速原型复杂业务、团队协作、长期维护

六、实战最佳实践与避坑指南

6.1 数据初始化放哪里?

场景推荐位置原因
依赖 URL 参数的数据onLoad只有这里能拿到 options
不依赖参数的静态数据data() / ref()声明即初始化
每次进入都需要刷新的数据onShow返回时也能刷新
需要 DOM 就绪的数据onReady如地图中心点、canvas 尺寸
全局共享数据App.globalData / Pinia跨页面共享

6.2 常见坑点速查

坑点现象解决方案
onLoad 中跳转白屏App 未完全初始化改用 onShowsetTimeout 延迟跳转
onMounted 中操作 map 报错原生视图未就绪改用 onReady
onShow 中拿不到最新参数onShowoptions 参数onLoad 中存到 dataonShow 中读取
onPageScroll 卡顿高频触发导致通信开销用 CSS position: sticky 替代,或节流处理
onUnload 不触发使用了 switchTab 而非 navigateBackTab 页用 onHide 代替
globalData 修改后页面不更新非响应式onShow 中重新赋值,或改用 Pinia
H5 端 onLoad 参数丢失浏览器刷新导致H5 端建议用 Vue Router 的 route.query 辅助

6.3 生命周期选择决策树

需要在某个时机执行代码?
    │
    ├─ 与 App 进程相关?
    │   ├─ 初始化/SDK ──► onLaunch
    │   ├─ 前后台切换 ──► onShow / onHide
    │   └─ 全局错误 ──► onError
    │
    ├─ 与页面导航相关?
    │   ├─ 接收参数 ──► onLoad
    │   ├─ 每次显示刷新 ──► onShow
    │   ├─ 返回拦截 ──► onBackPress
    │   └─ 页面销毁清理 ──► onUnload
    │
    ├─ 与视图渲染相关?
    │   ├─ 操作原生组件 ──► onReady
    │   ├─ DOM 操作(Vue) ──► onMounted
    │   └─ 组件卸载 ──► onUnmounted
    │
    └─ 与用户交互相关?
        ├─ 下拉刷新 ──► onPullDownRefresh
        ├─ 触底加载 ──► onReachBottom
        ├─ 滚动监听 ──► onPageScroll (⚠️慎用)
        └─ 分享 ──► onShareAppMessage

七、总结:四端生命周期全景对照表

uni-appiOSAndroid鸿蒙Vue3层级
onLaunchdidFinishLaunchingApplication.onCreateAbilityStage.onCreateApp
onShowdidBecomeActiveonResumeonForegroundApp
onHidedidEnterBackgroundonPauseonBackgroundApp
onExitwillTerminateonDestroyonDestroyapp.onUnmountApp
onLoadviewDidLoadonCreateaboutToAppearonBeforeMountPage
onShow (页面)viewWillAppearonResumeonPageShowonMountedPage
onReadyviewDidAppearonWindowFocusChangedonDidBuildonMountedPage
onHide (页面)viewWillDisappearonPauseonPageHidePage
onUnloaddeinitonDestroyaboutToDisappearonUnmountedPage

💡 给多端原生工程师的一句话: uni-app 的生命周期设计是对 iOS/Android/鸿蒙三者最大公约数的抽象。onLoad ≈ viewDidLoad ≈ onCreate ≈ aboutToAppearonShow ≈ viewWillAppear ≈ onResume ≈ onPageShow。掌握了这个映射关系,你就能在四种技术栈之间自如切换。框架在变,生命周期的本质思想从未改变。


📚 参考资料: