uni-app 生命周期详解
本文将详细介绍 uni-app 的生命周期,包括应用生命周期、页面生命周期和组件生命周期,帮助您更好地理解和使用 uni-app。
概述
生命周期是指应用程序从创建到销毁的整个过程中,特定时间点触发的函数,开发者可以在这些函数中执行自己的代码逻辑。
uni-app 生命周期分类
类型 | 说明 | 监听位置 |
---|---|---|
应用生命周期 | 应用级别的生命周期函数 | App.vue 中监听 |
页面生命周期 | 页面级别的生命周期函数 | 页面中监听 |
组件生命周期 | 组件级别的生命周期函数 | 组件中监听 |
应用生命周期
应用生命周期是指 uni-app 从启动到卸载的过程中,特定时间点触发的函数。这些函数在 App.vue
中定义。
生命周期函数
函数名 | 说明 | 参数 | 适用场景 |
---|---|---|---|
onLaunch | 应用初始化完成时触发(全局只触发一次) | options(启动参数) | 初始化应用数据、检查更新、获取用户信息 |
onShow | 应用启动或从后台进入前台时触发 | options(启动参数) | 恢复应用状态、刷新数据、统计使用时长 |
onHide | 应用从前台进入后台时触发 | - | 暂停音视频播放、保存应用状态、暂停定时器 |
onError | 应用运行报错时触发 | err(错误信息) | 错误日志收集、异常上报 |
onUniNViewMessage | 监听 nvue 页面发送的数据 | event(消息对象) | 接收 nvue 页面发送的消息 |
应用生命周期示例
<script>
export default {
onLaunch: function(options) {
console.log('App Launch')
console.log('启动参数:', options)
// 初始化应用数据
this.checkUpdate()
this.getUserInfo()
},
onShow: function(options) {
console.log('App Show')
console.log('显示参数:', options)
// 恢复应用状态
this.resumeAudio()
this.startTimer()
},
onHide: function() {
console.log('App Hide')
// 保存应用状态
this.pauseAudio()
this.stopTimer()
},
onError: function(err) {
console.log('App Error')
console.error(err)
// 错误上报
this.reportError(err)
},
methods: {
checkUpdate() {
// 检查更新逻辑
uni.getUpdateManager && uni.getUpdateManager().onCheckForUpdate((res) => {
if (res.hasUpdate) {
console.log('发现新版本')
}
})
},
getUserInfo() {
// 获取用户信息逻辑
uni.getUserInfo({
success: (res) => {
console.log('用户信息:', res.userInfo)
}
})
},
resumeAudio() {
// 恢复音频播放逻辑
const audioContext = uni.createInnerAudioContext()
audioContext.play()
},
pauseAudio() {
// 暂停音频播放逻辑
const audioContext = uni.createInnerAudioContext()
audioContext.pause()
},
startTimer() {
// 启动定时器逻辑
this.timer = setInterval(() => {
console.log('定时任务执行')
}, 60000)
},
stopTimer() {
// 停止定时器逻辑
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
},
reportError(err) {
// 错误上报逻辑
uni.request({
url: 'https://api.example.com/error-report',
method: 'POST',
data: {
error: err.toString(),
timestamp: Date.now()
}
})
}
}
}
</script>
页面生命周期
页面生命周期是指 uni-app 页面从加载到卸载的过程中,特定时间点触发的函数。这些函数在页面的 vue 文件中定义。
页面生命周期执行顺序
页面加载 → onLoad → onShow → onReady
页面切换 → onHide → 其他页面 → onShow
页面卸载 → onUnload
生命周期函数
函数名 | 说明 | 参数 | 触发时机 |
---|---|---|---|
onLoad | 页面加载时触发 | options(页面参数) | 页面加载时,只触发一次 |
onShow | 页面显示时触发 | - | 页面显示/切入前台时 |
onReady | 页面初次渲染完成时触发 | - | 页面初次渲染完成时,只触发一次 |
onHide | 页面隐藏时触发 | - | 页面隐藏/切入后台时 |
onUnload | 页面卸载时触发 | - | 页面卸载时 |
onPullDownRefresh | 下拉刷新时触发 | - | 用户下拉刷新时 |
onReachBottom | 上拉触底时触发 | - | 页面滚动到底部时 |
onPageScroll | 页面滚动时触发 | Object | 页面滚动时 |
onTabItemTap | tab 点击时触发 | Object | 点击 tab 时(仅 tabBar 页面) |
页面生命周期详解
onLoad - 页面加载
触发时机:页面加载时触发,只会触发一次
参数:options(上个页面传递的数据)
适用场景:
- 获取页面参数
- 初始化页面数据
- 发起网络请求获取初始数据
onLoad: function(options) {
console.log('页面加载完成')
console.log('页面参数:', options)
// 初始化页面数据
this.productId = options.id
this.loadProductDetail(options.id)
}
onShow - 页面显示
触发时机:页面显示/切入前台时触发
适用场景:
- 刷新页面数据
- 恢复页面状态
- 开始播放音视频等
onShow: function() {
console.log('页面显示')
// 刷新购物车数据
this.refreshCartData()
// 恢复页面状态
this.resumeVideoPlay()
}
onReady - 页面渲染完成
触发时机:页面初次渲染完成时触发,只会触发一次
适用场景:
- 获取页面元素
- 初始化图表等复杂组件
- 执行需要在页面渲染完成后进行的操作
onReady: function() {
console.log('页面初次渲染完成')
// 获取页面元素
const query = uni.createSelectorQuery()
query.select('#chart').boundingClientRect(data => {
// 初始化图表
this.initChart(data.width, data.height)
}).exec()
}
onHide - 页面隐藏
触发时机:页面隐藏/切入后台时触发
适用场景:
- 暂停音视频播放
- 保存页面状态
- 暂停定时器等耗电操作
onHide: function() {
console.log('页面隐藏')
// 暂停视频播放
this.pauseVideo()
// 保存编辑状态
this.saveEditState()
// 暂停定时器
clearInterval(this.timer)
}
onUnload - 页面卸载
触发时机:页面卸载时触发
适用场景:
- 清理页面资源
- 取消网络请求
- 关闭长连接
onUnload: function() {
console.log('页面卸载')
// 清理资源
this.disposeChart()
// 取消网络请求
this.cancelRequest()
// 关闭长连接
this.closeSocket()
}
onPullDownRefresh - 下拉刷新
触发时机:用户下拉刷新时触发
适用场景:
- 刷新页面数据
- 重新加载资源
注意:需要在 pages.json 中配置 enablePullDownRefresh
为 true
onPullDownRefresh: function() {
console.log('用户下拉刷新')
// 重新加载数据
this.loadData().then(() => {
// 数据加载完成后停止下拉刷新
uni.stopPullDownRefresh()
})
}
onReachBottom - 上拉触底
触发时机:页面滚动到底部时触发
适用场景:
- 加载更多数据
- 实现分页加载
注意:可以在 pages.json 中配置 onReachBottomDistance
设置触发距离
onReachBottom: function() {
console.log('页面滚动到底部')
if (!this.isLoading && !this.isEnd) {
this.isLoading = true
this.page++
this.loadMoreData().then(() => {
this.isLoading = false
})
}
}
onPageScroll - 页面滚动
触发时机:页面滚动时触发
参数:Object,包含 scrollTop 属性,表示页面在垂直方向已滚动的距离(单位 px)
适用场景:
- 实现吸顶效果
- 显示/隐藏回到顶部按钮
- 根据滚动位置调整 UI
注意:频繁触发,注意节流处理
onPageScroll: function(e) {
// 使用节流函数处理滚动事件
if (this.scrollTimer) return
this.scrollTimer = setTimeout(() => {
this.scrollTimer = null
// 根据滚动位置显示/隐藏回到顶部按钮
this.showBackToTop = e.scrollTop > 100
// 实现吸顶效果
this.isFixed = e.scrollTop > 200
}, 100)
}
onTabItemTap - Tab 点击
触发时机:点击 tab 时触发,仅在 tabBar 页面中存在
参数:Object,包含 pagePath、text、index 属性
适用场景:
- 统计 tab 点击事件
- 点击当前 tab 刷新页面
- 自定义 tab 点击行为
onTabItemTap: function(e) {
console.log('点击tab', e)
// 如果点击的是当前tab,刷新页面
if (e.index === this.currentTabIndex) {
this.refreshPage()
}
// 统计点击事件
this.reportTabClick(e.index)
}
页面生命周期完整示例
<template>
<view class="page">
<view class="content">
<text class="title">页面生命周期示例</text>
<view class="data-area">
<text>数据: {{ dataList.length }} 条</text>
</view>
<view class="button-area">
<button type="primary" @click="refreshData">刷新数据</button>
</view>
</view>
<view class="list-area">
<view v-for="(item, index) in dataList" :key="index" class="list-item">
{{ item.name }}
</view>
</view>
<view class="loading" v-if="isLoading">加载中...</view>
<view class="back-to-top" v-if="showBackToTop" @click="backToTop">
<text class="back-icon">↑</text>
</view>
</view>
</template>
<script>
export default {
data() {
return {
dataList: [],
page: 1,
isLoading: false,
isEnd: false,
showBackToTop: false,
scrollTimer: null,
productId: ''
}
},
// 页面加载
onLoad(options) {
console.log('页面加载', options)
this.productId = options.id || ''
this.loadData()
},
// 页面显示
onShow() {
console.log('页面显示')
this.refreshCartData()
},
// 页面渲染完成
onReady() {
console.log('页面初次渲染完成')
const query = uni.createSelectorQuery()
query.select('.content').boundingClientRect(data => {
console.log('内容区域高度:', data.height)
}).exec()
},
// 页面隐藏
onHide() {
console.log('页面隐藏')
this.savePageState()
},
// 页面卸载
onUnload() {
console.log('页面卸载')
if (this.scrollTimer) {
clearTimeout(this.scrollTimer)
}
},
// 下拉刷新
onPullDownRefresh() {
console.log('下拉刷新')
this.page = 1
this.isEnd = false
this.loadData().then(() => {
uni.stopPullDownRefresh()
})
},
// 上拉触底
onReachBottom() {
console.log('上拉加载更多')
if (!this.isLoading && !this.isEnd) {
this.loadMoreData()
}
},
// 页面滚动
onPageScroll(e) {
if (this.scrollTimer) return
this.scrollTimer = setTimeout(() => {
this.scrollTimer = null
this.showBackToTop = e.scrollTop > 100
}, 100)
},
methods: {
// 加载初始数据
loadData() {
this.isLoading = true
return new Promise((resolve) => {
setTimeout(() => {
this.dataList = Array.from({length: 20}, (_, i) => ({
id: i + 1,
name: `商品${i + 1}`
}))
this.isLoading = false
resolve()
}, 1000)
})
},
// 加载更多数据
loadMoreData() {
if (this.page >= 5) {
this.isEnd = true
return Promise.resolve()
}
this.isLoading = true
return new Promise((resolve) => {
setTimeout(() => {
const moreData = Array.from({length: 20}, (_, i) => ({
id: this.dataList.length + i + 1,
name: `商品${this.dataList.length + i + 1}`
}))
this.dataList = [...this.dataList, ...moreData]
this.page++
this.isLoading = false
resolve()
}, 1000)
})
},
// 刷新数据
refreshData() {
this.page = 1
this.isEnd = false
this.loadData()
},
// 刷新购物车数据
refreshCartData() {
console.log('刷新购物车数据')
},
// 保存页面状态
savePageState() {
console.log('保存页面状态')
uni.setStorageSync('pageState', {
scrollTop: this.scrollTop,
page: this.page
})
},
// 返回顶部
backToTop() {
uni.pageScrollTo({
scrollTop: 0,
duration: 300
})
}
}
}
</script>
<style scoped>
.page {
padding: 20px;
}
.content {
margin-bottom: 20px;
}
.title {
font-size: 18px;
font-weight: bold;
margin-bottom: 10px;
}
.data-area {
margin: 10px 0;
color: #666;
}
.button-area {
margin: 15px 0;
}
.list-area {
margin-top: 20px;
}
.list-item {
padding: 15px;
border-bottom: 1px solid #eee;
background-color: #fff;
}
.loading {
text-align: center;
padding: 15px;
color: #999;
}
.back-to-top {
position: fixed;
right: 20px;
bottom: 30px;
width: 40px;
height: 40px;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #fff;
z-index: 999;
}
.back-icon {
font-size: 20px;
}
</style>
组件生命周期
uni-app 组件生命周期遵循 Vue 组件生命周期,同时也支持 uni-app 页面生命周期。
Vue 2 组件生命周期
生命周期 | 说明 | 适用场景 |
---|---|---|
beforeCreate | 组件实例被创建之前 | 组件选项对象已创建,但实例还未创建 |
created | 组件实例已创建 | 初始化数据、调用 API 等 |
beforeMount | 组件挂载之前 | 模板已编译,但还未挂载到 DOM |
mounted | 组件挂载完成 | 获取 DOM 元素、初始化第三方库等 |
beforeUpdate | 组件更新之前 | 数据已更新,但 DOM 还未重新渲染 |
updated | 组件更新完成 | 更新后的 DOM 操作 |
beforeDestroy | 组件销毁之前 | 清理定时器、取消事件监听等 |
destroyed | 组件销毁完成 | 组件实例已经销毁 |
Vue 3 组件生命周期
生命周期 | 说明 | 对应 Vue 2 | 适用场景 |
---|---|---|---|
setup | 组件初始化 | beforeCreate/created | 组合式 API 的入口 |
onBeforeMount | 组件挂载之前 | beforeMount | 挂载前的准备工作 |
onMounted | 组件挂载完成 | mounted | 获取 DOM 元素、初始化第三方库等 |
onBeforeUpdate | 组件更新之前 | beforeUpdate | 更新前的准备工作 |
onUpdated | 组件更新完成 | updated | 更新后的 DOM 操作 |
onBeforeUnmount | 组件卸载之前 | beforeDestroy | 清理定时器、取消事件监听等 |
onUnmounted | 组件卸载完成 | destroyed | 组件实例已经销毁 |
组件生命周期示例
Vue 2 组件示例
<template>
<view class="component">
<text>{{ message }}</text>
<button @click="updateMessage">更新消息</button>
</view>
</template>
<script>
export default {
data() {
return {
message: '初始消息',
timer: null
}
},
beforeCreate() {
console.log('beforeCreate: 组件实例被创建之前')
},
created() {
console.log('created: 组件实例已创建')
// 初始化数据
this.initData()
},
beforeMount() {
console.log('beforeMount: 组件挂载之前')
},
mounted() {
console.log('mounted: 组件挂载完成')
// 启动定时器
this.timer = setInterval(() => {
console.log('定时器运行中...')
}, 2000)
},
beforeUpdate() {
console.log('beforeUpdate: 组件更新之前')
},
updated() {
console.log('updated: 组件更新完成')
},
beforeDestroy() {
console.log('beforeDestroy: 组件销毁之前')
// 清理定时器
if (this.timer) {
clearInterval(this.timer)
}
},
destroyed() {
console.log('destroyed: 组件销毁完成')
},
methods: {
initData() {
console.log('初始化数据')
},
updateMessage() {
this.message = '更新后的消息: ' + Date.now()
}
}
}
</script>
<style scoped>
.component {
padding: 20px;
text-align: center;
}
button {
margin-top: 10px;
padding: 10px 20px;
background-color: #007aff;
color: white;
border: none;
border-radius: 4px;
}
</style>
Vue 3 组件示例
<template>
<view class="component">
<text>{{ message }}</text>
<button @click="updateMessage">更新消息</button>
</view>
</template>
<script setup>
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from 'vue'
// 响应式数据
const message = ref('初始消息')
let timer = null
// 生命周期钩子
console.log('setup: 组件初始化')
onBeforeMount(() => {
console.log('onBeforeMount: 组件挂载之前')
})
onMounted(() => {
console.log('onMounted: 组件挂载完成')
// 启动定时器
timer = setInterval(() => {
console.log('定时器运行中...')
}, 2000)
})
onBeforeUpdate(() => {
console.log('onBeforeUpdate: 组件更新之前')
})
onUpdated(() => {
console.log('onUpdated: 组件更新完成')
})
onBeforeUnmount(() => {
console.log('onBeforeUnmount: 组件卸载之前')
// 清理定时器
if (timer) {
clearInterval(timer)
}
})
onUnmounted(() => {
console.log('onUnmounted: 组件卸载完成')
})
// 方法
function updateMessage() {
message.value = '更新后的消息: ' + Date.now()
}
// 初始化数据
function initData() {
console.log('初始化数据')
}
// 执行初始化
initData()
</script>
<style scoped>
.component {
padding: 20px;
text-align: center;
}
button {
margin-top: 10px;
padding: 10px 20px;
background-color: #007aff;
color: white;
border: none;
border-radius: 4px;
}
</style>
生命周期最佳实践
使用建议
建议 | 说明 |
---|---|
合理使用生命周期 | 根据不同的业务需求选择合适的生命周期钩子,避免在不必要的钩子中执行复杂操作 |
避免阻塞渲染 | 在 onLoad 和 onShow 等生命周期中避免执行耗时操作,可能会阻塞页面渲染 |
资源管理 | 在 onUnload 和 beforeDestroy 中清理资源,如定时器、事件监听等 |
数据获取 | 页面数据获取建议在 onLoad 中进行,而不是 onShow 中,避免重复请求 |
状态恢复 | 在 onShow 中恢复页面状态,如音视频播放状态 |
常见问题与解决方案
1. 页面返回后数据不刷新
问题:从 A 页面跳转到 B 页面,再从 B 页面返回 A 页面,A 页面数据没有刷新。
原因:返回上一页时,触发的是 onShow 而不是 onLoad。
解决方案:
- 在 onShow 中刷新数据
- 使用页面栈管理,在返回时刷新数据
- 使用全局事件总线,在 B 页面操作完成后触发事件,A 页面监听事件并刷新数据
// A页面
onShow() {
// 判断是否从B页面返回
const pages = getCurrentPages()
if (pages.length > 1 && pages[pages.length - 1].route.includes('pageA')) {
this.refreshData()
}
}
// 或者使用全局事件
onLoad() {
uni.$on('updateData', () => {
this.refreshData()
})
}
onUnload() {
uni.$off('updateData')
}
// B页面操作完成后
uni.$emit('updateData')
2. 页面生命周期执行顺序问题
问题:不清楚页面跳转时各个生命周期的执行顺序。
解决方案:了解页面跳转时的生命周期执行顺序:
- A 页面跳转到 B 页面:
A.onHide
→B.onLoad
→B.onShow
→B.onReady
- B 页面返回 A 页面:
B.onUnload
→A.onShow
3. 组件生命周期与页面生命周期的关系
问题:不清楚组件生命周期与页面生命周期的执行顺序和关系。
解决方案:了解组件和页面生命周期的关系:
- 页面加载时:
页面onLoad
→组件beforeCreate
→组件created
→组件beforeMount
→组件mounted
→页面onShow
→页面onReady
- 页面卸载时:
页面onUnload
→组件beforeDestroy
→组件destroyed
4. 内存泄漏问题
问题:页面跳转后,原页面的定时器、事件监听等资源未释放,导致内存泄漏。
解决方案:在页面卸载时清理资源:
onLoad() {
// 创建定时器
this.timer = setInterval(() => {
this.doSomething()
}, 1000)
// 添加事件监听
uni.$on('customEvent', this.handleEvent)
}
onUnload() {
// 清理定时器
if (this.timer) {
clearInterval(this.timer)
this.timer = null
}
// 移除事件监听
uni.$off('customEvent', this.handleEvent)
}
总结
uni-app 生命周期是开发过程中的重要概念,正确理解和使用生命周期可以:
- 提高应用性能
- 避免内存泄漏
- 优化用户体验
- 简化状态管理
建议在实际开发中,根据具体业务需求合理选择生命周期钩子,并注意资源的及时清理。