
摘要
在 UniApp 开发中,深刻理解生命周期是构建稳定、高效应用的关键。本文将深入剖析 UniApp 中应用生命周期 、页面生命周期 和组件生命周期的完整流程,通过详尽的代码示例、执行顺序分析和清晰的流程图,帮助你彻底掌握数据初始化、页面导航、组件通信等场景下的最佳实践,让你的 UniApp 开发之路更加顺畅。
一、 生命周期概述:为什么它如此重要?
生命周期 是指一个程序从创建、运行到销毁的整个过程。在 UniApp 中,生命周期函数是框架在特定阶段自动触发的"钩子函数"(Hook)。我们可以在这些钩子函数中编写代码,以在恰当的时机执行相应的逻辑,例如:
- 在应用启动时初始化全局数据
- 在页面加载时请求接口数据
- 在页面显示时开始动画
- 在页面隐藏时停止耗电操作
- 在组件挂载时订阅事件
- 在组件销毁时清理定时器
理解并善用生命周期,是 UniApp 开发者从入门到精通的必经之路。
二、 应用生命周期(App.vue)
应用生命周期函数定义在根目录下的 App.vue 中,它监控的是整个应用的全局状态。
核心钩子函数
onLaunch:应用初始化 时触发,全局只触发一次。是冷启动时的首个生命周期。onShow:应用启动 或从后台 切换到前台时触发。onHide:应用从前台 切换到后台时触发(如点击手机Home键,切换App)。onError:应用发生脚本错误或 API 调用失败时触发。onUniNViewMessage:对nvue页面发送的数据进行监听。
代码示例 (App.vue)
vue
<script>
export default {
onLaunch: function(options) {
console.log('🎉 App初始化完成 - onLaunch');
console.log('启动路径:', options.path);
console.log('启动场景值:', options.scene);
// 初始化全局数据,如用户登录状态
this.initUserInfo();
},
onShow: function(options) {
console.log('📱 App显示 - onShow', options);
// 应用进入前台,可以恢复一些任务,如音乐播放
},
onHide: function() {
console.log('🚫 App隐藏 - onHide');
// 应用进入后台,可以暂停一些耗电任务,如地理位置监听
},
onError: function(error) {
console.error('❌ 全局错误捕获 - onError:', error);
// 上报错误日志到服务器
this.reportError(error);
},
methods: {
initUserInfo() {
// 从本地存储获取用户信息
const userInfo = uni.getStorageSync('userInfo');
if (userInfo) {
this.globalData.userInfo = userInfo;
console.log('全局用户信息已初始化');
}
},
reportError(error) {
// 模拟上报错误
console.log('上报错误信息:', error);
}
},
globalData: {
userInfo: null,
appVersion: '1.0.0'
}
}
</script>
<style>
/* 全局样式 */
</style>
三、 页面生命周期 (Page.vue)
页面生命周期函数定义在每个页面的 .vue 文件中,它监控的是单个页面的状态变化。
核心钩子函数及执行顺序
下图清晰地展示了一个页面从创建到销毁的完整生命周期流程,特别是注意 onLoad、onShow、onReady 的执行顺序:
是 否 页面初始化 onLoad
接收参数 onShow
页面显示 首次加载? onReady
DOM挂载完成 用户操作/路由 跳转到新页面 当前页 onHide 返回当前页 卸载页面 onUnload
页面销毁
详细代码示例 (pages/index/index.vue)
vue
<template>
<view class="content">
<text>{{ title }}</text>
<text>接收到的参数: {{ queryData }}</text>
<button @click="navigateToDetail">跳转到详情页</button>
<button @click="redirectToDetail">重定向到详情页</button>
</view>
</template>
<script>
export default {
data() {
return {
title: '页面生命周期演示',
queryData: '',
timer: null
}
},
// 1. 页面加载(监听页面加载,其参数为上个页面传递的数据)
onLoad: function(options) {
console.log('1️⃣ 页面加载 - onLoad', '参数:', options);
this.queryData = JSON.stringify(options);
// 模拟从网络请求数据
this.fetchData();
// 启动一个定时器(注意要在onHide或onUnload中清理)
this.timer = setInterval(() => {
console.log('定时器运行中...');
}, 3000);
},
// 2. 页面显示(监听页面显示,每次打开页面都会调用)
onShow: function() {
console.log('2️⃣ 页面显示 - onShow');
// 页面每次显示时执行,如刷新列表数据
},
// 3. 页面初次渲染完成(监听页面初次渲染完成)
onReady: function() {
console.log('3️⃣ 页面就绪 - onReady');
// 此时页面已准备好,可以操作DOM(在H5端)
// 在小程序端,可以操作小程序DOM
},
// 4. 页面隐藏(监听页面隐藏)
onHide: function() {
console.log('4️⃣ 页面隐藏 - onHide');
// 页面跳转时,当前页会触发onHide
// 可以暂停定时器、动画等
if (this.timer) {
clearInterval(this.timer);
console.log('⏹️ 定时器已暂停');
}
},
// 5. 页面卸载(监听页面卸载)
onUnload: function() {
console.log('5️⃣ 页面卸载 - onUnload');
// 页面被关闭或重定向时触发
// 必须在这里清理全局事件、长连接等,防止内存泄漏
if (this.timer) {
clearInterval(this.timer);
console.log('🧹 定时器已清理');
}
},
// 6. 下拉刷新(监听用户下拉刷新动作)
onPullDownRefresh: function() {
console.log('⬇️ 下拉刷新 - onPullDownRefresh');
// 开始下拉刷新动画
uni.showLoading({
title: '刷新中...'
});
// 模拟数据刷新
setTimeout(() => {
this.title = '数据已更新:' + new Date().toLocaleTimeString();
// 停止下拉刷新动画
uni.stopPullDownRefresh();
uni.hideLoading();
uni.showToast({
title: '刷新成功',
icon: 'success'
});
}, 1500);
},
// 7. 上拉触底(监听用户上拉触底事件)
onReachBottom: function() {
console.log('⬆️ 上拉触底 - onReachBottom');
// 通常用于加载更多数据
this.loadMoreData();
},
// 8. 页面滚动(监听页面滚动)
onPageScroll: function(e) {
// 注意:此函数频繁触发,不要在此处执行复杂操作
// console.log('📜 页面滚动:', e.scrollTop);
},
methods: {
fetchData() {
console.log('📡 模拟请求数据...');
setTimeout(() => {
this.title = '数据加载完成';
}, 1000);
},
loadMoreData() {
uni.showLoading({
title: '加载中...'
});
setTimeout(() => {
this.title += ' - 已加载更多';
uni.hideLoading();
}, 1000);
},
navigateToDetail() {
// 保留当前页,跳转到新页面 - 会触发当前页的onHide
uni.navigateTo({
url: '/pages/detail/detail?id=123&name=uni-app'
});
},
redirectToDetail() {
// 关闭当前页,跳转到新页面 - 会触发当前页的onUnload
uni.redirectTo({
url: '/pages/detail/detail?id=456&name=redirect'
});
}
}
}
</script>
<style>
.content {
padding: 30rpx;
}
</style>
页面生命周期执行场景总结
| 场景 | 触发的生命周期 | 说明 |
|---|---|---|
| 页面首次加载 | onLoad → onShow → onReady |
完整的初始化流程 |
从A页面 navigateTo B页面 |
A: onHide |
A页面被隐藏但未销毁 |
| 从B页面返回A页面 | A: onShow |
A页面重新显示 |
从A页面 redirectTo B页面 |
A: onUnload |
A页面被完全销毁 |
| 下拉刷新 | onPullDownRefresh |
需要手动停止刷新动画 |
| 滚动到底部 | onReachBottom |
用于加载更多数据 |
四、 组件生命周期 (Component.vue)
UniApp 自定义组件的生命周期与 Vue 组件的生命周期基本一致。
核心钩子函数
beforeCreate:实例初始化之后,数据观测之前调用。created:实例创建完成,数据观测已完成,但DOM未挂载。beforeMount:在挂载开始之前被调用。mounted:实例挂载到DOM后调用,可以操作DOM。beforeUpdate:数据更新时调用,发生在虚拟DOM重新渲染和打补丁之前。updated:数据更新导致虚拟DOM重新渲染和打补丁之后调用。beforeDestroy:实例销毁之前调用。destroyed:实例销毁后调用。
代码示例 (components/my-component.vue)
vue
<template>
<view class="custom-component">
<text>组件计数: {{ count }}</text>
<button @click="count++">增加计数</button>
<button @click="unmountComponent">卸载组件</button>
</view>
</template>
<script>
export default {
name: 'MyComponent',
data() {
return {
count: 0,
intervalId: null
}
},
// 1. 组件初始化之前
beforeCreate() {
console.log('🔧 组件 - beforeCreate', '数据未初始化');
},
// 2. 组件创建完成
created() {
console.log('✅ 组件 - created', '数据已初始化, count:', this.count);
// 可以调用API,但无法操作DOM
this.intervalId = setInterval(() => {
console.log('组件内部定时器');
}, 5000);
},
// 3. 组件挂载前
beforeMount() {
console.log('📦 组件 - beforeMount');
},
// 4. 组件挂载完成
mounted() {
console.log('🎯 组件 - mounted', '可以操作DOM');
},
// 5. 数据更新前
beforeUpdate() {
console.log('🔄 组件 - beforeUpdate', 'count将要变为:', this.count);
},
// 6. 数据更新完成
updated() {
console.log('📊 组件 - updated', 'count已更新为:', this.count);
},
// 7. 组件销毁前
beforeDestroy() {
console.log('⚠️ 组件 - beforeDestroy', '准备清理资源');
},
// 8. 组件销毁完成
destroyed() {
console.log('🗑️ 组件 - destroyed', '组件已销毁');
// 清理定时器,防止内存泄漏
if (this.intervalId) {
clearInterval(this.intervalId);
console.log('🧹 组件定时器已清理');
}
},
methods: {
unmountComponent() {
// 在实际使用中,通常由父组件控制组件的显示隐藏
console.log('手动触发组件卸载');
}
}
}
</script>
<style>
.custom-component {
margin: 20rpx;
padding: 20rpx;
border: 1px solid #eee;
border-radius: 10rpx;
}
</style>
在页面中使用组件
vue
<template>
<view>
<text>父页面</text>
<my-component v-if="showComponent" />
<button @click="toggleComponent">切换组件显示</button>
</view>
</template>
<script>
import MyComponent from '@/components/my-component.vue'
export default {
components: {
MyComponent
},
data() {
return {
showComponent: true
}
},
methods: {
toggleComponent() {
this.showComponent = !this.showComponent;
}
}
}
</script>
五、 完整生命周期执行顺序实战
让我们通过一个实际场景来看完整的生命周期执行顺序:
场景:应用启动 → 进入首页 → 跳转到详情页 → 返回首页 → 关闭应用
执行顺序日志
// 1. 应用启动
🎉 App初始化完成 - onLaunch
📱 App显示 - onShow
// 2. 进入首页
1️⃣ 页面加载 - onLoad (首页)
2️⃣ 页面显示 - onShow (首页)
3️⃣ 页面就绪 - onReady (首页)
// 3. 组件初始化 (如果首页使用了组件)
🔧 组件 - beforeCreate
✅ 组件 - created
📦 组件 - beforeMount
🎯 组件 - mounted
// 4. 跳转到详情页
4️⃣ 页面隐藏 - onHide (首页)
1️⃣ 页面加载 - onLoad (详情页)
2️⃣ 页面显示 - onShow (详情页)
3️⃣ 页面就绪 - onReady (详情页)
// 5. 返回首页
4️⃣ 页面隐藏 - onHide (详情页)
5️⃣ 页面卸载 - onUnload (详情页)
2️⃣ 页面显示 - onShow (首页)
// 6. 应用进入后台 (点击Home键)
🚫 App隐藏 - onHide
// 7. 应用回到前台
📱 App显示 - onShow
// 8. 组件更新 (如果数据变化)
🔄 组件 - beforeUpdate
📊 组件 - updated
六、 最佳实践与常见陷阱
✅ 最佳实践
- 数据初始化 :在
onLoad中接收参数并初始化页面数据。 - 接口请求 :在
onLoad或onShow中请求数据,根据业务需求选择:onLoad:数据不需要频繁刷新onShow:数据需要实时更新(如返回页面时刷新)
- DOM操作 :在
onReady(页面)或mounted(组件)中进行。 - 资源清理 :在
onUnload(页面)或beforeDestroy/destroyed(组件)中清理定时器、事件监听等,防止内存泄漏。 - 全局状态 :在
onLaunch中初始化全局数据。
❌ 常见陷阱
- 在
onLoad中操作DOM:此时DOM还未渲染完成,操作无效。 - 忘记清理定时器/事件:导致内存泄漏,应用卡顿。
- 在
onPageScroll中执行复杂操作:导致页面滚动卡顿。 - 混淆
onShow和onLoad:onLoad只触发一次,onShow每次页面显示都会触发。
七、 总结
UniApp 的生命周期体系设计清晰,层次分明:
- 应用生命周期:管理整个应用的生老病死
- 页面生命周期:控制单个页面的导航流转
- 组件生命周期:处理组件内部的状态变化
掌握这三层生命周期的执行时机和适用场景,能够让你在 UniApp 开发中游刃有余,写出更加健壮、高效的代码。记住:在正确的时机做正确的事,这正是生命周期钩子函数存在的意义。
希望本文能帮助你彻底理解 UniApp 的生命周期机制,如有任何疑问,欢迎在评论区留言讨论!