在 uni-app 跨平台开发中,钩子函数是串联 “业务逻辑” 与 “页面 / 组件生命周期” 的关键纽带。新手开发者常因钩子函数繁多而困惑,其实日常开发中仅需掌握12 个高频钩子函数,就能应对 90% 以上的场景。本文将剔除冷门钩子,聚焦应用、页面、组件三大层级的常用钩子,通过 “作用解析 + 场景示例 + 代码实战” 的形式,帮你快速上手并灵活运用。
一、先搞懂:钩子函数的 “层级逻辑”
在学习具体钩子前,必须明确 uni-app 的钩子函数按 “作用范围” 分为三大层级,不同层级的钩子互不干扰、各司其职:
应用层级:控制整个 App 的全局行为(如启动、前台 / 后台切换),定义在
App.vue
中,全局仅执行一次。页面层级:控制单个页面的生命周期(如加载、显示、卸载),定义在页面组件(
pages/xxx/xxx.vue
)中,每次页面跳转都会触发。组件层级:控制自定义组件的生命周期(如创建、渲染、销毁),定义在组件(
components/xxx.vue
)中,基于 Vue 2 生命周期设计,与页面钩子完全独立。
记住这个层级逻辑,能避免后续混淆 “页面钩子” 与 “组件钩子” 的使用场景。
二、应用层级:3 个常用钩子,掌控全局
应用层级的钩子函数虽少,但作用至关重要,直接影响 App 的全局初始化与资源管理。日常开发中高频使用的仅有 3 个:onLaunch
、onShow
、onHide
。
2.1 onLaunch:App 初始化的 “第一入口”
调用时机:App 首次启动时触发(全局仅执行一次,关闭 App 后重新打开才会再次触发)。
核心作用:初始化全局状态(如 Vuex)、检查登录状态、加载全局配置(如接口基础 URL)。
注意事项:此时页面尚未渲染,不能操作 DOM 或调用页面相关 API(如
uni.navigateTo
需延迟执行)。
场景示例:App 启动检查登录状态
<!-- App.vue -->
<script>
export default {
onLaunch(options) {
console.log("App初始化完成,启动参数:", options); // 可获取启动时的参数(如小程序跳转参数)
// 1. 初始化Vuex全局状态(如加载用户信息)
this.$store.dispatch("initGlobalState");
// 2. 检查登录状态(从本地存储获取token)
const token = uni.getStorageSync("token");
if (!token) {
// 延迟跳转:避免页面未就绪导致跳转失败
setTimeout(() => {
uni.redirectTo({ url: "/pages/login/login" }); // 跳转到登录页
}, 500);
}
}
};
</script>
2.2 onShow:App 切换前台的 “唤醒开关”
调用时机:App 从后台切换到前台时触发(如用户按 Home 键返回桌面后,再次点击 App 图标打开),每次切换都会触发。
核心作用:恢复页面状态(如刷新购物车数量)、重启全局定时器 / 监听(如实时消息推送)。
场景对比:与页面的
onShow
不同,应用的onShow
是 “全局级” 的,适合处理整个 App 的前台恢复逻辑。
场景示例:前台切换时刷新全局数据
<!-- App.vue -->
<script>
export default {
data() {
return {
globalTimer: null // 全局定时器
};
},
onShow() {
console.log("App切换到前台");
// 1. 刷新全局数据(如购物车数量、未读消息数)
this.$store.dispatch("refreshCartCount");
this.$store.dispatch("refreshUnreadMessage");
// 2. 重启全局定时器(如实时获取时间)
this.globalTimer = setInterval(() => {
this.$store.commit("updateCurrentTime", new Date().toLocaleTimeString());
}, 1000);
}
};
</script>
2.3 onHide:App 切换后台的 “休眠按钮”
调用时机:App 从前台切换到后台时触发(如用户按 Home 键、切换到其他应用),每次切换都会触发。
核心作用:暂停耗时操作(如视频播放、音频播放)、清除全局定时器 / 监听(避免后台耗电、内存泄漏)。
场景示例:后台切换时释放全局资源
<!-- App.vue -->
<script>
export default {
data() {
return {
globalTimer: null
};
},
onHide() {
console.log("App切换到后台");
// 1. 清除全局定时器
clearInterval(this.globalTimer);
// 2. 暂停全局媒体播放(如视频、音频)
const videoContext = uni.createVideoContext("global-video");
videoContext.pause();
// 3. 取消全局事件监听(如WebSocket)
this.$store.dispatch("closeWebSocket");
}
};
</script>
三、页面层级:6 个常用钩子,掌控页面交互
页面层级的钩子函数是日常开发中使用频率最高的,涵盖页面从 “加载” 到 “卸载” 的全流程。高频使用的有 6 个:onLoad
、onShow
、onReady
、onHide
、onUnload
、onPullDownRefresh
(含onReachBottom
)。
3.1 onLoad:页面 “初始化” 的核心钩子
调用时机:页面加载完成时触发(仅执行一次,即使页面隐藏后重新显示,也不会再次触发)。
核心作用:接收页面跳转参数、请求页面初始化数据、初始化页面状态(如分页参数)。
关键能力:通过参数
options
获取跳转时携带的参数(如/pages/detail/detail?id=123
中的id
)。
场景示例:页面加载时获取详情数据
<!-- pages/detail/detail.vue(商品详情页) -->
<script>
export default {
data() {
return {
goodsDetail: null // 商品详情数据
};
},
// 接收跳转参数(如?id=123)
onLoad(options) {
console.log("页面加载,商品ID:", options.id); // { id: "123" }
// 请求商品详情数据
this.getGoodsDetail(options.id);
},
methods: {
getGoodsDetail(goodsId) {
uni.request({
url: `https://your-server.com/api/goods/${goodsId}`,
success: (res) => {
this.goodsDetail = res.data.data;
},
fail: () => {
uni.showToast({ title: "获取详情失败", icon: "none" });
}
});
}
}
};
</script>
3.2 onShow:页面 “显示” 的触发开关
调用时机:页面显示时触发(每次页面从隐藏状态切换到显示状态都会触发,如从详情页返回列表页)。
核心作用:刷新页面数据(如列表页返回后刷新列表)、重启页面级定时器 / 监听(如倒计时)。
与 onLoad 的区别:
onLoad
仅执行一次,onShow
可多次执行,适合处理 “页面重新显示时需要更新” 的逻辑。
场景示例:列表页返回后刷新数据
<!-- pages/list/list.vue(商品列表页) -->
<script>
export default {
data() {
return {
goodsList: [] // 商品列表数据
};
},
onLoad() {
this.getGoodsList(); // 首次加载数据
},
// 从详情页返回时,重新获取列表数据
onShow() {
this.getGoodsList();
},
methods: {
getGoodsList() {
uni.request({
url: "https://your-server.com/api/goods/list",
success: (res) => {
this.goodsList = res.data.data;
}
});
}
}
};
</script>
3.3 onReady:页面 “渲染完成” 的标志
调用时机:页面渲染完成时触发(仅执行一次,此时页面 DOM 已生成,可操作 DOM 元素)。
核心作用:操作页面 DOM(如获取元素高度、初始化第三方组件)、执行需要 DOM 支持的逻辑(如地图渲染)。
注意事项:uni-app 中不支持
document
/window
,需用uni.createSelectorQuery()
获取 DOM。
场景示例:获取页面元素高度
<!-- pages/index/index.vue(首页) -->
<script>
export default {
onReady() {
console.log("页面渲染完成,可操作DOM");
// 获取首页轮播图高度
uni.createSelectorQuery()
.in(this) // 绑定当前页面上下文(必须)
.select(".swiper-container") // 选择器
.boundingClientRect((rect) => {
if (rect) {
console.log("轮播图高度:", rect.height); // 如 300px
// 可根据高度动态调整其他元素样式
}
})
.exec(); // 执行查询
}
};
</script>
3.4 onHide & onUnload:页面 “隐藏 / 卸载” 的资源清理
这两个钩子常配合使用,核心作用都是 “释放资源”,但触发时机不同:
onHide:页面隐藏时触发(如跳转到其他页面,但页面仍保留在页面栈中,未被销毁),需清除 “临时资源”(如定时器,后续可能重启)。
onUnload:页面卸载时触发(如关闭页面、跳转到其他页面且当前页面被销毁),需清除 “永久资源”(如接口请求、全局事件监听)。
场景示例:页面隐藏 / 卸载时清理资源
<!-- pages/timer/timer.vue(倒计时页面) -->
<script>
export default {
data() {
return {
countdown: 60,
timer: null // 倒计时定时器
};
},
onLoad() {
// 开启倒计时定时器
this.startCountdown();
},
// 页面隐藏时:清除定时器(后续返回可重启)
onHide() {
clearInterval(this.timer);
},
// 页面卸载时:彻底清除资源(如取消接口请求)
onUnload() {
clearInterval(this.timer);
this.cancelUnfinishedRequest(); // 取消未完成的接口请求
},
methods: {
startCountdown() {
this.timer = setInterval(() => {
if (this.countdown > 0) {
this.countdown--;
} else {
clearInterval(this.timer);
}
}, 1000);
},
cancelUnfinishedRequest() {
// 实际项目中可使用axios的CancelToken等机制
console.log("取消未完成的接口请求");
}
}
};
</script>
3.5 onPullDownRefresh & onReachBottom:下拉刷新与上拉加载
这两个钩子是列表页的 “标配”,用于实现下拉刷新数据、上拉加载更多的功能:
onPullDownRefresh:用户下拉页面时触发,需在
pages.json
中配置enablePullDownRefresh: true
开启。onReachBottom:用户上拉页面触底时触发,可在
pages.json
中配置onReachBottomDistance
调整触底距离(默认 50px)。
场景示例:列表页下拉刷新与上拉加载
<!-- pages/list/list.vue(商品列表页) -->
<script>
export default {
data() {
return {
goodsList: [],
page: 1, // 当前页码
pageSize: 10, // 每页条数
isLoading: false // 加载状态锁(避免重复请求)
};
},
onLoad() {
this.getGoodsList();
},
// 下拉刷新:重新请求第一页数据
onPullDownRefresh() {
this.page = 1;
this.getGoodsList(() => {
uni.stopPullDownRefresh(); // 关闭下拉刷新动画
});
},
// 上拉触底:请求下一页数据
onReachBottom() {
if (this.isLoading) return; // 若正在加载,跳过
this.page++;
this.getGoodsList();
},
methods: {
getGoodsList(callback) {
this.isLoading = true;
uni.request({
url: `https://your-server.com/api/goods/list?page=${this.page}&size=${this.pageSize}`,
success: (res) => {
const newList = res.data.data;
if (this.page === 1) {
this.goodsList = newList; // 第一页:覆盖数据
} else {
this.goodsList = [...this.goodsList, ...newList]; // 后续页:追加数据
}
},
fail: () => {
uni.showToast({ title: "请求失败", icon: "none" });
if (this.page > 1) this.page--; // 请求失败,页码回退
},
complete: () => {
this.isLoading = false;
callback && callback(); // 执行回调(如下拉刷新关闭动画)
}
});
}
}
};
</script>
<!-- pages.json 配置 -->
{
"pages": [
{
"path": "pages/list/list",
"style": {
"enablePullDownRefresh": true, // 开启下拉刷新
"onReachBottomDistance": 100 // 触底距离调整为100px
}
}
]
}
四、组件层级:3 个常用钩子,掌控组件行为
组件层级的钩子函数基于 Vue 2 生命周期,日常开发中高频使用的有 3 个:created
、mounted
、beforeDestroy
(含watch
监听)。
4.1 created:组件 “初始化” 的核心
调用时机:组件实例创建完成时触发(数据已初始化,但 DOM 未渲染)。
核心作用:初始化组件私有数据、请求组件专属数据、绑定组件内部事件。
注意事项:此时组件未挂载到 DOM,不能操作 DOM 元素。
场景示例:组件初始化时请求数据
<!-- components/GoodsCard.vue(商品卡片组件) -->
<script>
export default {
props: {
goodsId: {
type: String,
required: true // 父组件必须传递商品ID
}
},
data() {
return {
goodsInfo: null // 组件私有数据(商品信息)
};
},
// 组件创建完成,请求商品信息
created() {
this.getGoodsInfo(this.goodsId);
},
methods: {
getGoodsInfo(id) {
uni.request({
url: `https://your-server.com/api/goods/${id}`,
success: (res) => {
this.goodsInfo = res.data.data;
}
});
}
}
};
</script>
4.2 mounted:组件 “渲染完成” 的标志
调用时机:组件挂载到 DOM 后触发(此时组件 DOM 已生成,可操作 DOM)。
核心作用:操作组件 DOM(如初始化组件内部的第三方插件)、绑定 DOM 事件(如点击、滚动)。
与页面 onReady 的区别:
mounted
是组件级的,仅在组件渲染完成后触发;onReady
是页面级的,在所有子组件渲染完成后触发。
场景示例:组件挂载后初始化日历插件
<!-- components/DatePicker.vue(日期选择组件) -->
<script>
// 假设引入了第三方日历插件
import Calendar from "@/utils/calendar.js";
export default {
mounted() {
console.log("组件挂载完成,初始化日历插件");
// 获取组件内部DOM元素,初始化日历
const calendarEl = this.$el.querySelector(".calendar-container");
this.calendar = new Calendar(calendarEl, {
minDate: new Date(), // 最小日期为今天
onSelect: (date) => {
// 日期选择回调,向父组件发送事件
this.$emit("date-select", date);
}
});
}
};
</script>
4.3 beforeDestroy:组件 “销毁前” 的资源清理
调用时机:组件销毁前触发(此时组件实例仍可用,可访问数据和方法)。
核心作用:清除组件内部的定时器、取消事件监听、释放第三方插件资源(避免内存泄漏)。
注意事项:组件销毁后无法再操作实例,所有资源清理逻辑需在此钩子中完成。
场景示例:组件销毁前清理资源
<!-- components/DatePicker.vue(日期选择组件) -->
<script>
import Calendar from "@/utils/calendar.js";
export default {
data() {
return {
calendar: null,
timer: null // 组件内部定时器
};
},
mounted() {
// 初始化日历插件
const calendarEl = this.$el.querySelector(".calendar-container");
this.calendar = new Calendar(calendarEl, { /* 配置项 */ });
// 开启组件内部定时器(如实时更新日期)
this.timer = setInterval(() => {
this.calendar.updateCurrentDate();
}, 60000);
},
// 组件销毁前清理资源
beforeDestroy() {
console.log("组件即将销毁,清理资源");
// 1. 销毁第三方插件实例
this.calendar.destroy();
// 2. 清除定时器
clearInterval(this.timer);
// 3. 取消事件监听(如组件内绑定的全局事件)
uni.off("global-event", this.handleGlobalEvent);
},
methods: {
handleGlobalEvent() {
// 处理全局事件的逻辑
}
}
};
</script>
4.4 补充:watch 监听(组件数据同步的 “利器”)
虽然watch
不是严格意义上的 “生命周期钩子”,但它常与组件钩子配合使用,用于监听props
或data
的变化,是组件数据同步的核心工具。
核心作用:当
props
(父组件传递的数据)或data
(组件私有数据)变化时,执行自定义逻辑(如同步更新组件内部状态)。常用配置:
* `deep: true`:深度监听对象 / 数组内部属性的变化。
* `immediate: true`:初始值赋值时立即触发监听(默认仅变化时触发)。
场景示例:监听 props 变化同步数据
<!-- components/GoodsCard.vue(商品卡片组件) -->
<script>
export default {
props: {
goodsId: {
type: String,
required: true
}
},
data() {
return {
goodsInfo: null
};
},
created() {
this.getGoodsInfo(this.goodsId);
},
// 监听goodsId变化(如父组件切换商品时)
watch: {
goodsId: {
handler(newGoodsId) {
// 当goodsId变化时,重新请求对应商品信息
this.getGoodsInfo(newGoodsId);
},
immediate: true // 初始赋值时也触发(确保created中无需重复调用)
}
},
methods: {
getGoodsInfo(id) {
uni.request({
url: `https://your-server.com/api/goods/${id}`,
success: (res) => {
this.goodsInfo = res.data.data;
}
});
}
}
};
</script>
五、关键:钩子函数的 “执行顺序”(避坑必备)
日常开发中,很多问题源于不了解钩子函数的执行顺序(如 “数据未初始化就使用”)。以下是 3 个高频场景的执行顺序,必须牢记:
5.1 场景 1:App 首次启动(应用 + 首页钩子)
应用层级:
onLaunch
(App 初始化)→onShow
(App 切换前台)页面层级:
onLoad
(首页加载)→onShow
(首页显示)→onReady
(首页渲染完成)
示例控制台输出:
App初始化完成,启动参数:{...} // onLaunch
App切换到前台 // onShow(应用)
页面加载,商品ID:123 // onLoad(首页)
页面显示 // onShow(首页)
页面渲染完成,可操作DOM // onReady(首页)
5.2 场景 2:页面跳转(A 页面→B 页面)
A 页面:
onHide
(A 页面隐藏)B 页面:
onLoad
(B 页面加载)→onShow
(B 页面显示)→onReady
(B 页面渲染完成)从 B 页面返回 A 页面:
B 页面:
onUnload
(B 页面卸载)A 页面:
onShow
(A 页面重新显示)
示例控制台输出:
// A→B跳转
A页面隐藏 // onHide(A)
B页面加载,参数:{type: "1"} // onLoad(B)
B页面显示 // onShow(B)
B页面渲染完成 // onReady(B)
// B→A返回
B页面卸载 // onUnload(B)
A页面显示 // onShow(A)
5.3 场景 3:页面加载组件(页面 + 组件钩子)
页面层级:
onLoad
(页面加载)组件层级:
created
(组件创建)→mounted
(组件挂载)页面层级:
onShow
(页面显示)→onReady
(页面渲染完成)
示例控制台输出:
页面加载,参数:{} // onLoad(页面)
组件创建完成,请求商品信息 // created(组件)
组件挂载完成,初始化日历插件 // mounted(组件)
页面显示 // onShow(页面)
页面渲染完成,可操作DOM // onReady(页面)
六、避坑指南:5 个高频问题与解决方案
6.1 问题 1:onLaunch 中跳转页面失败
现象:在onLaunch
中调用uni.navigateTo
跳转页面,无响应或报错。
原因:onLaunch
执行时页面栈尚未初始化,无法承载页面跳转。
解决方案:用setTimeout
延迟 500ms 跳转,优先使用uni.redirectTo
(不依赖页面栈深度):
onLaunch() {
if (!uni.getStorageSync("token")) {
setTimeout(() => {
uni.redirectTo({ url: "/pages/login/login" });
}, 500);
}
}
6.2 问题 2:onPullDownRefresh 不触发
现象:下拉页面无刷新动画,onPullDownRefresh
未执行。
原因:未在pages.json
中开启当前页面的下拉刷新配置。
解决方案:在pages.json
中添加enablePullDownRefresh: true
:
{
"pages": [
{
"path": "pages/list/list",
"style": {
"enablePullDownRefresh": true,
"backgroundColor": "#f5f5f5" // 刷新区域背景色
}
}
]
}
6.3 问题 3:组件 mounted 中获取 DOM 为 null
现象:在组件mounted
中用this.$el.querySelector
获取元素,返回null
。
原因:元素通过v-if
控制,mounted
执行时v-if
条件为false
,元素未渲染。
解决方案:用this.$nextTick
延迟执行 DOM 操作,确保元素已渲染:
mounted() {
this.showElement = true; // 先让v-if条件为true
this.$nextTick(() => {
// 延迟到DOM更新后获取元素
const el = this.$el.querySelector(".target-element");
console.log("元素:", el);
});
}
6.4 问题 4:watch 监听对象不触发
现象:监听的对象内部属性变化时,watch
未执行。
原因:未配置deep: true
,watch
默认仅监听对象引用变化,不监听内部属性。
解决方案:添加deep: true
配置:
watch: {
userInfo: {
handler(newVal) {
console.log("用户信息变化:", newVal);
},
deep: true // 深度监听内部属性
}
}
6.5 问题 5:页面隐藏后定时器仍运行
现象:页面跳转后,定时器继续执行,导致内存泄漏。
原因:仅在onUnload
中清除定时器,而onUnload
仅在页面卸载时触发(uni.navigateTo
跳转时页面仅隐藏,不卸载)。
解决方案:在onHide
中清除定时器,onShow
中重新开启:
onLoad() {
this.startTimer();
}
onShow() {
this.startTimer(); // 页面重新显示时重启定时器
}
onHide() {
clearInterval(this.timer); // 页面隐藏时清除定时器
}
onUnload() {
clearInterval(this.timer); // 页面卸载时彻底清除
}
methods: {
startTimer() {
this.timer = setInterval(() => {
// 定时器逻辑
}, 1000);
}
}
七、总结:常用钩子函数 “速查表”
为方便快速查阅,整理了本文讲解的 12 个高频钩子函数速查表:
层级 | 钩子函数 | 核心作用 | 关键场景 |
---|---|---|---|
应用层级 | onLaunch |
App 初始化、检查登录状态、全局配置 | App 首次启动 |
onShow |
刷新全局数据、重启全局定时器 | App 切换前台 | |
onHide |
暂停全局操作、清除全局定时器 | App 切换后台 | |
页面层级 | onLoad |
接收参数、请求初始化数据 | 页面首次加载 |
onShow |
刷新页面数据、重启页面定时器 | 页面重新显示(如返回页面) | |
onReady |
操作 DOM、初始化第三方组件 | 页面渲染完成 | |
onHide |
清除页面临时资源(如定时器) | 页面隐藏(如跳转其他页面) | |
onUnload |
释放页面永久资源(如接口请求) | 页面卸载(如关闭页面) | |
onPullDownRefresh |
下拉刷新数据 | 用户下拉页面 | |
onReachBottom |
上拉加载更多数据 | 用户上拉触底 | |
组件层级 | created |
初始化组件数据、请求组件专属数据 | 组件实例创建 |
mounted |
操作组件 DOM、初始化组件插件 | 组件挂载完成 | |
beforeDestroy |
清除组件资源(定时器、事件监听) | 组件销毁前 | |
辅助工具 | watch |
监听数据变化、同步组件状态 | props 或data 变化时 |
八、实战建议:如何高效使用钩子函数?
按层级划分逻辑:全局逻辑(如登录检查)放应用钩子,页面逻辑(如列表加载)放页面钩子,组件逻辑(如卡片渲染)放组件钩子,避免混乱。
资源 “成对” 处理:开启定时器 / 监听时,明确在哪个钩子清除(如
onShow
开启→onHide
清除,mounted
开启→beforeDestroy
清除),避免内存泄漏。优先使用高频钩子:非特殊场景下,优先使用本文讲解的 12 个高频钩子,冷门钩子(如
onError
、beforeCreate
)尽量少用,降低复杂度。结合业务场景选择:如 “页面重新显示需刷新数据” 用
onShow
,“仅首次加载需请求数据” 用onLoad
;“组件数据同步” 用watch
+created
。
掌握这些常用钩子函数及实战技巧,你就能轻松应对 uni-app 开发中的绝大多数场景,写出高效、稳定的跨平台代码。