组件生命周期
介绍
本文档详细介绍 RuoYi-Plus-UniApp 项目中 Vue 3 组件和 UniApp 页面的生命周期,包括各个钩子函数的执行时机、使用场景和注意事项。
核心内容:
- Vue 3 组件生命周期 - setup、挂载、更新、卸载阶段
- UniApp 页面生命周期 - onLoad、onShow、onHide 等
- UniApp 应用生命周期 - onLaunch、onShow、onHide
- 生命周期执行顺序 - 各钩子的执行时序
- 常见使用场景 - 数据获取、事件监听、资源清理
Vue 3 组件生命周期
生命周期图示
创建阶段
│
▼
┌─────────────┐
│ setup() │ ← 组合式 API 入口
└─────────────┘
│
▼
┌─────────────┐
│ onBeforeMount│ ← 挂载前
└─────────────┘
│
▼
┌─────────────┐
│ onMounted │ ← 挂载完成(可访问 DOM)
└─────────────┘
│
▼
更新阶段 ←──────────┐
│ │
▼ │
┌─────────────┐ │
│onBeforeUpdate│ │ ← 数据变化触发
└─────────────┘ │
│ │
▼ │
┌─────────────┐ │
│ onUpdated │ ────┘
└─────────────┘
│
▼
卸载阶段
│
▼
┌─────────────┐
│onBeforeUnmount│ ← 卸载前
└─────────────┘
│
▼
┌─────────────┐
│ onUnmounted │ ← 卸载完成
└─────────────┘setup 阶段
setup 是组合式 API 的入口,在组件实例创建时执行:
vue
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
// setup 阶段执行的代码
console.log('setup 执行')
// 响应式数据初始化
const count = ref(0)
const user = ref(null)
// 计算属性定义
const doubleCount = computed(() => count.value * 2)
// 方法定义
const increment = () => {
count.value++
}
// 注意:此时无法访问 DOM
// document.querySelector('.xxx') // 可能为 null
</script>执行时机:
- 在
beforeCreate和created之间执行 - Props 已解析完成
- 无法访问 DOM 元素
适合的操作:
- 响应式数据初始化
- 计算属性定义
- 方法定义
- 组合式函数调用
onBeforeMount
组件挂载到 DOM 之前调用:
vue
<script lang="ts" setup>
import { onBeforeMount } from 'vue'
onBeforeMount(() => {
console.log('组件即将挂载')
// 此时 DOM 还未渲染
})
</script>执行时机:
- setup 执行完成后
- 模板编译完成
- DOM 尚未创建
适合的操作:
- 挂载前的最后准备
- 很少使用
onMounted
组件挂载完成后调用:
vue
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
const chartRef = ref<HTMLElement | null>(null)
const listData = ref([])
onMounted(() => {
console.log('组件已挂载')
// 可以访问 DOM 元素
console.log(chartRef.value)
// 初始化第三方库
initChart()
// 获取数据
fetchData()
// 添加事件监听
window.addEventListener('resize', handleResize)
})
const initChart = () => {
if (chartRef.value) {
// 初始化图表
}
}
const fetchData = async () => {
const res = await api.getList()
listData.value = res.data
}
const handleResize = () => {
// 处理窗口大小变化
}
</script>
<template>
<view ref="chartRef" class="chart"></view>
</template>执行时机:
- DOM 已渲染完成
- 子组件也已挂载
- 可以访问 DOM 元素
适合的操作:
- 访问/操作 DOM
- 初始化第三方库
- 发起 API 请求
- 添加事件监听
- 启动定时器
onBeforeUpdate
响应式数据变化导致重新渲染前调用:
vue
<script lang="ts" setup>
import { ref, onBeforeUpdate } from 'vue'
const count = ref(0)
onBeforeUpdate(() => {
console.log('组件即将更新')
console.log('当前 DOM 中的值:', document.querySelector('.count')?.textContent)
console.log('新的数据值:', count.value)
})
</script>
<template>
<view class="count">{{ count }}</view>
<button @click="count++">增加</button>
</template>执行时机:
- 响应式数据变化后
- DOM 更新之前
适合的操作:
- 访问更新前的 DOM 状态
- 很少使用
onUpdated
DOM 更新完成后调用:
vue
<script lang="ts" setup>
import { ref, onUpdated } from 'vue'
const messages = ref<string[]>([])
const listRef = ref<HTMLElement | null>(null)
onUpdated(() => {
console.log('组件已更新')
// 列表更新后滚动到底部
if (listRef.value) {
listRef.value.scrollTop = listRef.value.scrollHeight
}
})
const addMessage = (msg: string) => {
messages.value.push(msg)
}
</script>
<template>
<view ref="listRef" class="message-list">
<view v-for="msg in messages" :key="msg">{{ msg }}</view>
</view>
</template>执行时机:
- 任何响应式数据变化导致的 DOM 更新后
- 可能多次触发
适合的操作:
- 访问更新后的 DOM
- 执行依赖 DOM 的操作
注意事项:
- 不要在此修改响应式数据(可能导致无限循环)
onBeforeUnmount
组件卸载之前调用:
vue
<script lang="ts" setup>
import { onBeforeUnmount } from 'vue'
onBeforeUnmount(() => {
console.log('组件即将卸载')
// 清理工作
// 此时组件实例仍然可用
})
</script>执行时机:
- 组件即将被卸载
- 组件实例仍然完全可用
适合的操作:
- 准备清理工作
onUnmounted
组件卸载完成后调用:
vue
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue'
const timer = ref<number | null>(null)
onMounted(() => {
// 启动定时器
timer.value = setInterval(() => {
console.log('定时任务')
}, 1000)
// 添加事件监听
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
console.log('组件已卸载')
// 清除定时器
if (timer.value) {
clearInterval(timer.value)
timer.value = null
}
// 移除事件监听
window.removeEventListener('resize', handleResize)
// 断开 WebSocket 连接
// ws.close()
// 取消未完成的请求
// abortController.abort()
})
const handleResize = () => {
// 处理窗口大小变化
}
</script>执行时机:
- 组件完全卸载
- 所有子组件已卸载
- 所有响应式效果已停止
适合的操作:
- 清除定时器
- 移除事件监听
- 取消订阅
- 断开连接
- 释放资源
UniApp 页面生命周期
页面生命周期钩子
vue
<script lang="ts" setup>
import { onLoad, onShow, onHide, onUnload, onReady } from '@dcloudio/uni-app'
import { onMounted, onUnmounted } from 'vue'
// Vue 生命周期
onMounted(() => {
console.log('1. Vue onMounted')
})
// UniApp 页面生命周期
onLoad((options) => {
console.log('2. 页面加载', options)
// options 包含页面参数
// 如:pages/detail/detail?id=123
// options = { id: '123' }
})
onReady(() => {
console.log('3. 页面初次渲染完成')
})
onShow(() => {
console.log('4. 页面显示')
})
onHide(() => {
console.log('5. 页面隐藏')
})
onUnload(() => {
console.log('6. 页面卸载')
})
onUnmounted(() => {
console.log('7. Vue onUnmounted')
})
</script>onLoad
页面加载时触发:
vue
<script lang="ts" setup>
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
const detailId = ref('')
const detailData = ref(null)
onLoad((options) => {
// 获取页面参数
if (options?.id) {
detailId.value = options.id
loadDetail(options.id)
}
// 处理场景值(小程序)
if (options?.scene) {
handleScene(options.scene)
}
})
const loadDetail = async (id: string) => {
const res = await api.getDetail(id)
detailData.value = res.data
}
const handleScene = (scene: string) => {
// 处理扫码等场景
const params = decodeURIComponent(scene)
console.log('场景参数:', params)
}
</script>执行时机:
- 页面加载时
- 仅执行一次(页面被缓存时不再触发)
参数:
options: 页面路径参数
onShow
页面显示时触发:
vue
<script lang="ts" setup>
import { ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
const cartCount = ref(0)
onShow(() => {
console.log('页面显示')
// 每次显示时刷新数据
refreshCartCount()
// 检查登录状态
checkLoginStatus()
})
const refreshCartCount = async () => {
const res = await api.getCartCount()
cartCount.value = res.data
}
const checkLoginStatus = () => {
const token = uni.getStorageSync('token')
if (!token) {
// 未登录处理
}
}
</script>执行时机:
- 页面首次显示
- 从其他页面返回
- 从后台切到前台
适合的操作:
- 刷新页面数据
- 检查状态变化
- 恢复动画/音视频
onHide
页面隐藏时触发:
vue
<script lang="ts" setup>
import { ref } from 'vue'
import { onHide } from '@dcloudio/uni-app'
const videoContext = ref(null)
onHide(() => {
console.log('页面隐藏')
// 暂停视频播放
if (videoContext.value) {
videoContext.value.pause()
}
// 保存草稿
saveDraft()
// 暂停定位
stopLocationUpdate()
})
const saveDraft = () => {
const draft = { /* 草稿数据 */ }
uni.setStorageSync('draft', draft)
}
const stopLocationUpdate = () => {
uni.stopLocationUpdate()
}
</script>执行时机:
- 跳转到其他页面
- 切到后台
- 页面被覆盖
适合的操作:
- 暂停媒体播放
- 保存临时数据
- 停止位置更新
onUnload
页面卸载时触发:
vue
<script lang="ts" setup>
import { onUnload } from '@dcloudio/uni-app'
onUnload(() => {
console.log('页面卸载')
// 清理资源
cleanup()
// 取消请求
cancelRequests()
})
const cleanup = () => {
// 清理 WebSocket
// 清理定时器
// 移除事件监听
}
const cancelRequests = () => {
// 取消未完成的网络请求
}
</script>执行时机:
uni.navigateBack返回uni.redirectTo重定向uni.reLaunch重启应用
onReady
页面初次渲染完成时触发:
vue
<script lang="ts" setup>
import { ref } from 'vue'
import { onReady } from '@dcloudio/uni-app'
const canvasContext = ref(null)
onReady(() => {
console.log('页面渲染完成')
// 可以操作页面元素
initCanvas()
// 获取节点信息
getNodeInfo()
})
const initCanvas = () => {
canvasContext.value = uni.createCanvasContext('myCanvas')
// 绑画操作
}
const getNodeInfo = () => {
uni.createSelectorQuery()
.select('.my-element')
.boundingClientRect((rect) => {
console.log('元素信息:', rect)
})
.exec()
}
</script>执行时机:
- 页面首次渲染完成
- 仅触发一次
适合的操作:
- 操作 Canvas
- 获取节点信息
- 初始化需要 DOM 的第三方库
UniApp 应用生命周期
App.vue 中的生命周期
vue
<!-- App.vue -->
<script lang="ts" setup>
import { onLaunch, onShow, onHide, onError } from '@dcloudio/uni-app'
onLaunch((options) => {
console.log('应用启动', options)
// 初始化配置
initApp()
// 检查更新(小程序)
checkUpdate()
// 获取系统信息
getSystemInfo()
})
onShow((options) => {
console.log('应用显示', options)
// 从后台切回前台
// 可以处理推送点击等场景
if (options?.path) {
handleDeepLink(options)
}
})
onHide(() => {
console.log('应用隐藏')
// 切到后台
// 保存应用状态
saveAppState()
})
onError((error) => {
console.error('应用错误', error)
// 上报错误
reportError(error)
})
const initApp = () => {
// 初始化 SDK
// 加载全局配置
}
const checkUpdate = () => {
// #ifdef MP-WEIXIN
const updateManager = uni.getUpdateManager()
updateManager.onCheckForUpdate((res) => {
if (res.hasUpdate) {
console.log('有新版本')
}
})
updateManager.onUpdateReady(() => {
uni.showModal({
title: '更新提示',
content: '新版本已准备好,是否重启应用?',
success: (res) => {
if (res.confirm) {
updateManager.applyUpdate()
}
}
})
})
// #endif
}
const getSystemInfo = () => {
const systemInfo = uni.getSystemInfoSync()
console.log('系统信息:', systemInfo)
}
const handleDeepLink = (options: any) => {
// 处理深度链接
}
const saveAppState = () => {
// 保存应用状态
}
const reportError = (error: string) => {
// 上报错误到服务器
}
</script>生命周期执行顺序
页面首次加载
App onLaunch
↓
App onShow
↓
Page setup
↓
Page onLoad
↓
Page onMounted
↓
Page onShow
↓
Page onReady页面跳转
当前页面 onHide
↓
新页面 setup
↓
新页面 onLoad
↓
新页面 onMounted
↓
新页面 onShow
↓
新页面 onReady页面返回
当前页面 onUnload
↓
当前页面 onUnmounted
↓
上一页面 onShow应用切到后台
当前页面 onHide
↓
App onHide应用切回前台
App onShow
↓
当前页面 onShow常见使用场景
数据获取模式
vue
<script lang="ts" setup>
import { ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
const listData = ref([])
const pageParams = ref<Record<string, string>>({})
// 方式一:onLoad 中获取(仅首次)
onLoad((options) => {
pageParams.value = options || {}
fetchData()
})
// 方式二:onShow 中获取(每次显示)
onShow(() => {
refreshData()
})
// 首次加载
const fetchData = async () => {
const res = await api.getList(pageParams.value)
listData.value = res.data
}
// 刷新数据(可能只需要部分数据)
const refreshData = async () => {
// 只刷新需要实时更新的数据
const res = await api.getStatus()
// 更新状态
}
</script>资源管理模式
vue
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { onShow, onHide } from '@dcloudio/uni-app'
const timer = ref<number | null>(null)
const ws = ref<WebSocket | null>(null)
// 初始化资源
onMounted(() => {
// 创建 WebSocket 连接
ws.value = new WebSocket('wss://example.com')
// 启动定时器
timer.value = setInterval(heartbeat, 30000)
})
// 页面显示时恢复
onShow(() => {
if (ws.value?.readyState === WebSocket.CLOSED) {
reconnect()
}
})
// 页面隐藏时暂停
onHide(() => {
// 可选:暂停某些操作
})
// 清理资源
onUnmounted(() => {
if (timer.value) {
clearInterval(timer.value)
}
if (ws.value) {
ws.value.close()
}
})
const heartbeat = () => {
ws.value?.send(JSON.stringify({ type: 'ping' }))
}
const reconnect = () => {
ws.value = new WebSocket('wss://example.com')
}
</script>页面状态恢复
vue
<script lang="ts" setup>
import { ref } from 'vue'
import { onLoad, onUnload, onShow } from '@dcloudio/uni-app'
const formData = ref({
title: '',
content: ''
})
const DRAFT_KEY = 'form_draft'
// 加载草稿
onLoad(() => {
const draft = uni.getStorageSync(DRAFT_KEY)
if (draft) {
formData.value = draft
}
})
// 页面显示时检查
onShow(() => {
// 可以检查是否有新的草稿
})
// 页面卸载时保存
onUnload(() => {
if (formData.value.title || formData.value.content) {
uni.setStorageSync(DRAFT_KEY, formData.value)
}
})
// 提交成功后清除草稿
const submit = async () => {
await api.submit(formData.value)
uni.removeStorageSync(DRAFT_KEY)
}
</script>最佳实践
1. 合理选择生命周期
typescript
// 初始化操作 → onLoad/onMounted
// 每次显示刷新 → onShow
// 资源清理 → onUnload/onUnmounted
// DOM 操作 → onReady/onMounted2. 避免在 setup 中执行异步操作
typescript
// ❌ 不推荐
const data = await fetchData() // setup 中不应直接 await
// ✅ 推荐
onMounted(async () => {
data.value = await fetchData()
})3. 配对使用创建和销毁
typescript
onMounted(() => {
window.addEventListener('resize', handler)
timer = setInterval(tick, 1000)
})
onUnmounted(() => {
window.removeEventListener('resize', handler)
clearInterval(timer)
})常见问题
1. onLoad 和 onMounted 的区别?
onLoad: UniApp 页面生命周期,可接收页面参数onMounted: Vue 组件生命周期,组件挂载完成
2. 页面缓存时生命周期表现?
使用 keep-alive 或 TabBar 页面:
- 首次进入:触发完整生命周期
- 再次进入:仅触发
onShow - 离开:仅触发
onHide
3. 子组件的生命周期执行顺序?
父组件 setup
↓
父组件 onBeforeMount
↓
子组件 setup
↓
子组件 onBeforeMount
↓
子组件 onMounted
↓
父组件 onMounted