uni-app 常用钩子函数:从场景到实战,掌握开发核心
在 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初始化完成,启动参数:{...} // onLaunchApp切换到前台 // 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条件为truethis.$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 开发中的绝大多数场景,写出高效、稳定的跨平台代码。