Skip to content

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 页面发送的消息

应用生命周期示例

vue
<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页面滚动时
onTabItemTaptab 点击时触发Object点击 tab 时(仅 tabBar 页面)

页面生命周期详解

onLoad - 页面加载

触发时机:页面加载时触发,只会触发一次

参数:options(上个页面传递的数据)

适用场景

  • 获取页面参数
  • 初始化页面数据
  • 发起网络请求获取初始数据
javascript
onLoad: function(options) {
  console.log('页面加载完成')
  console.log('页面参数:', options)
  
  // 初始化页面数据
  this.productId = options.id
  this.loadProductDetail(options.id)
}

onShow - 页面显示

触发时机:页面显示/切入前台时触发

适用场景

  • 刷新页面数据
  • 恢复页面状态
  • 开始播放音视频等
javascript
onShow: function() {
  console.log('页面显示')
  
  // 刷新购物车数据
  this.refreshCartData()
  // 恢复页面状态
  this.resumeVideoPlay()
}

onReady - 页面渲染完成

触发时机:页面初次渲染完成时触发,只会触发一次

适用场景

  • 获取页面元素
  • 初始化图表等复杂组件
  • 执行需要在页面渲染完成后进行的操作
javascript
onReady: function() {
  console.log('页面初次渲染完成')
  
  // 获取页面元素
  const query = uni.createSelectorQuery()
  query.select('#chart').boundingClientRect(data => {
    // 初始化图表
    this.initChart(data.width, data.height)
  }).exec()
}

onHide - 页面隐藏

触发时机:页面隐藏/切入后台时触发

适用场景

  • 暂停音视频播放
  • 保存页面状态
  • 暂停定时器等耗电操作
javascript
onHide: function() {
  console.log('页面隐藏')
  
  // 暂停视频播放
  this.pauseVideo()
  // 保存编辑状态
  this.saveEditState()
  // 暂停定时器
  clearInterval(this.timer)
}

onUnload - 页面卸载

触发时机:页面卸载时触发

适用场景

  • 清理页面资源
  • 取消网络请求
  • 关闭长连接
javascript
onUnload: function() {
  console.log('页面卸载')
  
  // 清理资源
  this.disposeChart()
  // 取消网络请求
  this.cancelRequest()
  // 关闭长连接
  this.closeSocket()
}

onPullDownRefresh - 下拉刷新

触发时机:用户下拉刷新时触发

适用场景

  • 刷新页面数据
  • 重新加载资源

注意:需要在 pages.json 中配置 enablePullDownRefresh 为 true

javascript
onPullDownRefresh: function() {
  console.log('用户下拉刷新')
  
  // 重新加载数据
  this.loadData().then(() => {
    // 数据加载完成后停止下拉刷新
    uni.stopPullDownRefresh()
  })
}

onReachBottom - 上拉触底

触发时机:页面滚动到底部时触发

适用场景

  • 加载更多数据
  • 实现分页加载

注意:可以在 pages.json 中配置 onReachBottomDistance 设置触发距离

javascript
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

注意:频繁触发,注意节流处理

javascript
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 点击行为
javascript
onTabItemTap: function(e) {
  console.log('点击tab', e)
  
  // 如果点击的是当前tab,刷新页面
  if (e.index === this.currentTabIndex) {
    this.refreshPage()
  }
  // 统计点击事件
  this.reportTabClick(e.index)
}

页面生命周期完整示例

vue
<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 组件示例

vue
<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 组件示例

vue
<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。

解决方案

  1. 在 onShow 中刷新数据
  2. 使用页面栈管理,在返回时刷新数据
  3. 使用全局事件总线,在 B 页面操作完成后触发事件,A 页面监听事件并刷新数据
javascript
// 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.onHideB.onLoadB.onShowB.onReady
  • B 页面返回 A 页面:B.onUnloadA.onShow

3. 组件生命周期与页面生命周期的关系

问题:不清楚组件生命周期与页面生命周期的执行顺序和关系。

解决方案:了解组件和页面生命周期的关系:

  • 页面加载时:页面onLoad组件beforeCreate组件created组件beforeMount组件mounted页面onShow页面onReady
  • 页面卸载时:页面onUnload组件beforeDestroy组件destroyed

4. 内存泄漏问题

问题:页面跳转后,原页面的定时器、事件监听等资源未释放,导致内存泄漏。

解决方案:在页面卸载时清理资源:

javascript
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 生命周期是开发过程中的重要概念,正确理解和使用生命周期可以:

  • 提高应用性能
  • 避免内存泄漏
  • 优化用户体验
  • 简化状态管理

建议在实际开发中,根据具体业务需求合理选择生命周期钩子,并注意资源的及时清理。

相关链接

一次开发,多端部署 - 让跨平台开发更简单