1. 从“页面白屏”说起:为什么必须搞懂UniApp生命周期?
前几天,一个刚接手UniApp项目的同事跑来找我,一脸困惑。他负责的页面在App端冷启动时,偶尔会先白屏一两秒,然后才正常显示数据。他检查了网络请求、数据加载逻辑,都没问题。我让他把onLoad和onShow里的代码给我看看,果然,问题就出在这里——他把一个本应在onShow里执行的、依赖页面已渲染完成的DOM操作,放在了onLoad里。这个看似微小的顺序错误,在复杂的原生渲染环境下被放大,导致了短暂的白屏。
这个案例让我觉得,是时候系统性地聊聊UniApp的生命周期了。这绝不是一个枯燥的API列表背诵题。对于任何使用UniApp进行跨平台开发的开发者而言,生命周期是你理解应用运行脉络、掌控页面状态、优化性能表现、乃至解决各种诡异Bug的“地图”。无论是处理App的冷热启动、小程序的页面栈管理,还是H5的路由跳转,生命周期都是贯穿始终的指挥棒。
简单来说,UniApp的生命周期是一系列在特定时间点由框架自动触发的函数。你的代码在这些函数里执行,就像演员在导演喊“Action”时开始表演。理解每个“Action”喊出的时机和上下文,你才能演好这出戏。很多人觉得生命周期就是onLoad,onShow,onHide那几个钩子,但真正用起来,尤其是在处理平台差异、状态恢复、性能优化时,才会发现里面的门道深着呢。
接下来,我会抛开简单的罗列,带你从应用启动到页面销毁,完整走一遍UniApp的生命周期旅程。我们会重点关注那些容易混淆的点、平台差异带来的“坑”,以及如何利用生命周期写出更健壮、高效的代码。无论你是刚接触UniApp的新手,还是已经写过一些页面但总觉得有些地方“不太顺”的开发者,相信这篇内容都能给你带来新的启发。
2. 应用级生命周期:整个App的“呼吸”与“心跳”
当我们启动一个UniApp应用时,最先登场的不是某个页面,而是应用本身的生命周期。它定义了应用从启动、运行到被销毁的完整过程。理解它,是处理全局状态、监听应用行为的基础。
2.1onLaunch:应用的“诞生”时刻
onLaunch是整个应用生命周期中第一个、也是最重要的一个钩子。它在应用初始化完成时触发,且在整个应用运行期间仅触发一次。你可以把它想象成应用的“构造函数”。
核心作用与常见场景:
- 获取启动参数:这是
onLaunch最典型的用途。当用户通过分享链接、扫码、或其他应用跳转打开你的UniApp时,携带的参数会在这里获取。// app.vue 或 App.vue (Vue 3 组合式API) export default { onLaunch(options) { console.log('App Launch, 启动参数:', options); // options 可能包含:path, query, scene, shareTicket等 // 例如小程序扫码进入,options.query 会包含二维码的参数 if (options.query && options.query.inviteCode) { // 处理邀请码逻辑 this.globalData.inviteCode = options.query.inviteCode; } } } - 初始化全局服务:在这里初始化一些全局只需要一次的服务,比如第三方SDK(如统计、推送、IM)、全局状态管理库(如Vuex或Pinia)的实例化、以及你自定义的全局工具类。
onLaunch() { // 初始化云开发(如果使用) wx.cloud.init({...}); // 初始化全局状态存储 this.globalData = { userInfo: null, systemInfo: null }; // 获取手机系统信息,用于后续的样式适配 uni.getSystemInfo({ success: (res) => { this.globalData.systemInfo = res; console.log('系统信息:', res); } }); } - 检查更新与热重启:对于App端,可以在这里检查是否有新版本,并提示用户更新。对于小程序端,可以在这里处理强制更新逻辑。
注意:
onLaunch的执行时机非常早,此时首页的onLoad可能还没有执行。因此,不要在这里执行耗时操作,否则会拖慢应用的首次渲染速度。复杂的初始化(如大型数据预加载)应考虑异步进行或放到首页的onLoad中。
2.2onShow与onHide:应用的“前台”与“后台”
这两个钩子标志着应用是否对用户可见,它们可以触发多次。
onShow:当应用从后台切换到前台显示时触发。例如,用户按了Home键将应用挂起,然后又从任务管理器切回来。onHide:当应用从前台切换到后台隐藏时触发。例如,用户按了Home键或切换到其他应用。
它们与页面级onShow/onHide的区别:应用级的钩子监听的是整个App的可见性变化,而页面级的只关心当前页面的可见性。例如,在App内从A页面跳转到B页面,A页面的onHide和B页面的onShow会触发,但应用级的onShow和onHide不会触发,因为整个App依然在前台。
典型应用场景:
- 数据同步与恢复:在
onShow中,可以检查登录态是否过期,并重新拉取用户信息或同步未保存的数据。 - 播放器控制:对于音视频播放类应用,通常在
onHide时暂停播放,在onShow时根据业务逻辑决定是否恢复播放。 - 统计与日志:记录用户每次打开和离开应用的时间,用于分析用户活跃度。
onShow() { console.log('App Show'); // 应用从后台唤醒,检查登录态 this.checkLoginStatus(); // 恢复全局定时器或动画 if (this.globalData.backgroundTimer) { this.resumeBackgroundTask(); } }, onHide() { console.log('App Hide'); // 应用进入后台,暂停不必要的任务以节省资源 this.pauseBackgroundTask(); // 如果需要,可以尝试保存当前的草稿或状态 this.autoSaveDraft(); }2.3onError与onUnhandledRejection:应用的“健康监测”
这两个是错误监听钩子,用于捕获全局的JavaScript异常和未处理的Promise拒绝。
onError:监听JavaScript执行错误。onUnhandledRejection:监听未被.catch()处理的Promise拒绝。
为什么它们很重要?在跨平台开发中,一些平台(如小程序)的原生环境对错误的容忍度较低,一个未捕获的错误可能导致整个页面白屏甚至应用闪退。通过全局监听,我们可以将错误信息上报到服务器,方便排查线上问题,同时可以给用户一个友好的错误提示,而不是一个崩溃的界面。
onError(err) { console.error('App全局错误捕获:', err); // 将错误信息、发生页面、设备信息等上报到监控平台 this.reportErrorToServer({ type: 'js_error', message: err.message, stack: err.stack, timestamp: Date.now() }); // 可以在这里展示一个全局的错误提示Toast(需谨慎,避免循环触发) // uni.showToast({ icon: 'none', title: '程序开小差了,请稍后再试' }); }, onUnhandledRejection(res) { console.error('未处理的Promise拒绝:', res); this.reportErrorToServer({ type: 'promise_rejection', reason: res.reason, timestamp: Date.now() }); }实操心得:在开发阶段,务必在开发者工具中打开“异常抛出时中断”选项,以便即时定位错误。但在生产环境,一定要有全局错误捕获,这是线上应用稳定性的最后一道防线。上报时记得做好信息过滤,避免上传敏感数据。
3. 页面级生命周期:单个页面的“人生轨迹”
如果说应用生命周期是舞台的灯光和幕布,那么页面生命周期就是台上每个演员的独白和走位。它是我们日常开发中接触最频繁的部分,直接决定了页面的数据流和交互逻辑。
3.1 加载阶段:onLoad->onShow->onReady
这是页面诞生和初次亮相的标准流程。
onLoad(option):页面加载时触发。一个页面只会调用一次。这是生命周期中最早可以获取到传递参数(option)的地方。- 核心任务:接收路由参数,并基于此参数初始化页面数据(如发起网络请求获取详情)。
onLoad(options) { console.log('页面加载,参数:', options); this.id = options.id; // 获取传入的ID if (this.id) { this.fetchDetailData(this.id); // 根据ID请求数据 } // 也可以在这里初始化一些页面内部的状态 this.loading = true; }- 与
created的区别:在Vue.js中,created钩子在实例创建完成后同步调用。在UniApp中,onLoad的调用时机与created非常接近,但onLoad能明确接收到页面参数。通常,我们将依赖页面参数的初始化放在onLoad,将不依赖参数的初始化放在created。
onShow():页面显示/切入前台时触发。每次打开页面都会调用,包括从其他页面返回、或从后台切回该页面。- 核心任务:处理页面显示时需要实时更新的内容。例如,从编辑页返回列表页,需要在
onShow中刷新列表数据。 - 与
onLoad的常见混淆点:onLoad只执行一次,用于“初始化”;onShow可能执行多次,用于“刷新”。文章开头提到的白屏问题,就是把一个需要等待页面基础DOM渲染完成才能执行的操作(比如计算某个元素高度),放在了onLoad里。此时视图可能还未准备好,导致计算错误或渲染异常。这类操作应该放在onReady或onShow中,并确保DOM已存在。
- 核心任务:处理页面显示时需要实时更新的内容。例如,从编辑页返回列表页,需要在
onReady():页面初次渲染完成时触发。一个页面只会调用一次,代表视图层已经准备好,可以和逻辑层进行交互了。- 核心任务:执行需要访问或操作DOM/组件的操作。例如,获取某个元素的节点信息(
uni.createSelectorQuery),或者初始化需要依赖DOM渲染的第三方库(如ECharts图表、地图组件)。
onReady() { // 此时视图已渲染,可以安全地操作DOM this.initChart(); // 初始化ECharts实例 this.getNavBarHeight(); // 获取导航栏高度用于布局计算 }- 注意:在小程序平台,
onReady的触发可能比Web端稍晚,因为它需要等待小程序视图层渲染完毕。
- 核心任务:执行需要访问或操作DOM/组件的操作。例如,获取某个元素的节点信息(
3.2 隐藏与卸载阶段:onHide->onUnload
这是页面离开舞台的过程。
onHide():页面隐藏/切入后台时触发。当navigateTo到其他页面、或应用切换到后台时触发。- 核心任务:暂停页面上的耗时任务,以节省性能和电量。例如,停止轮播图自动播放、暂停视频/音频播放、清除定时器等。
onHide() { clearInterval(this.dataPollingTimer); // 清除轮询定时器 if (this.videoContext) { this.videoContext.pause(); // 暂停视频播放 } }onUnload():页面卸载时触发。当页面被redirectTo、navigateBack(且目标不是该页面)或关闭页面时触发。- 核心任务:进行资源清理工作,防止内存泄漏。这是生命周期中最后一个可以执行清理操作的钩子。
- 必须清理的资源包括:
- 全局事件监听器(如
uni.$on, 但需注意作用域)。 - 在页面中创建的、但生命周期长于页面的对象(某些特殊的第三方库实例)。
- 手动创建的WebSocket连接或长连接。
- 全局事件监听器(如
onUnload() { // 移除全局事件监听(如果在当前页面监听的话) uni.$off('someEvent', this.eventHandler); // 关闭页面内创建的WebSocket if (this.socket && this.socket.readyState === WebSocket.OPEN) { this.socket.close(); } // 清除可能引起内存泄漏的引用 this.largeDataCache = null; }- 常见坑点:很多人会忘记在
onUnload中清理定时器。实际上,如果定时器是在onLoad或onShow中通过setInterval创建的,页面卸载后这个定时器可能仍然存在并继续执行,导致错误或内存泄漏。更安全的做法是在onHide中就清除,并在onShow中重新创建。
3.3 平台特有的页面生命周期
为了处理更复杂的跨平台场景,UniApp还提供了一些平台特有的生命周期钩子。
onPullDownRefresh():监听用户下拉刷新事件。需要在pages.json中为当前页面开启enablePullDownRefresh。通常在这里执行重新拉取最新数据的操作,并在完成后调用uni.stopPullDownRefresh()停止动画。onReachBottom():监听用户上拉触底事件。用于实现列表的加载更多(分页)功能。需要合理设置防抖和节流,避免频繁触发。onPageScroll(event):监听页面滚动事件。event.scrollTop可以获取页面在垂直方向已滚动的距离(单位px)。注意:此事件频繁触发,回调函数中应避免执行复杂或耗时操作,否则会影响滚动流畅度。onShareAppMessage():监听用户点击页面内转发按钮(<button open-type="share">)或右上角菜单“转发”按钮的行为。必须返回一个分享配置对象,定义转发内容的标题、路径、图像等。onNavigationBarButtonTap(event):监听原生导航栏按钮点击事件。event.index为按钮索引,在pages.json中定义。onBackPress(event):监听页面返回按钮(包括物理返回键、左上角返回按钮、安卓手势返回)事件。返回true可以阻止默认返回行为,实现自定义逻辑(如提示用户保存未提交的表单)。onBackPress(options) { if (this.formHasChanged) { uni.showModal({ title: '提示', content: '内容尚未保存,确定要返回吗?', success: (res) => { if (res.confirm) { // 用户确认返回,不拦截 } else if (res.cancel) { // 用户取消,拦截返回 return true; } } }); // 显示模态框期间,先拦截返回 return true; } // 默认不拦截 return false; }
4. 组件生命周期:Vue组件的“内在节奏”
在UniApp中,页面本身也是一个Vue组件,但它遵循上述的页面级生命周期。而页面内使用的自定义组件,则遵循标准的Vue组件生命周期。理解这两者的关系和执行顺序至关重要。
4.1 Vue 2 与 Vue 3 生命周期概览
UniApp支持Vue 2和Vue 3(组合式API)。它们的生命周期函数名称和用法略有不同。
Vue 2 选项式API生命周期:
beforeCreate:实例初始化之后,数据观测和事件配置之前。created:实例创建完成,已完成数据观测,但尚未挂载DOM。可以访问this,可以发起异步请求。beforeMount:挂载开始之前被调用。mounted:实例被挂载到DOM后调用。可以操作DOM,子组件不一定已挂载。beforeUpdate:数据更新时调用,发生在虚拟DOM打补丁之前。updated:数据更改导致的虚拟DOM重新渲染和打补丁之后调用。beforeDestroy:实例销毁之前调用。destroyed:实例销毁后调用。
Vue 3 组合式API生命周期(通过onX函数导入):
onBeforeMount/onMountedonBeforeUpdate/onUpdatedonBeforeUnmount/onUnmountedonErrorCaptured(捕获子孙组件错误)onRenderTracked/onRenderTriggered(用于调试渲染依赖)
4.2 页面与组件生命周期的执行顺序
这是一个非常关键的实践点。假设一个页面(Page)中包含一个自定义组件(Component),它们的生命周期触发顺序是怎样的?
页面初次加载时:
Page.beforeCreate -> Page.created -> Page.onLoad -> Component.beforeCreate -> Component.created -> Component.beforeMount -> Page.onShow -> Page.onReady -> Component.mounted关键观察:
- 页面的
onLoad在页面的Vue实例创建(created)之后,但在其内部组件的beforeCreate之前触发。这意味着你可以在onLoad中设置一些数据,这些数据可以作为props传递给子组件。 - 页面的
onShow在组件开始挂载(beforeMount)之后、但在组件完全挂载(mounted)之前触发。 - 页面的
onReady在所有子组件都挂载完成(mounted)之后触发。这是为什么onReady是执行DOM操作最安全时机的原因。
页面跳转时(Page A 跳转到 Page B):
PageA.onHide -> PageB.beforeCreate -> PageB.created -> PageB.onLoad -> (PageB子组件创建) -> PageB.onShow -> PageB.onReady页面返回时(从Page B 返回 Page A):
PageB.onUnload -> PageA.onShow注意,返回时Page A的onLoad不会再次触发,因为页面实例被保留在了小程序或App的页面栈中。
理解这个顺序,可以帮助你解决诸如“为什么在页面的created里获取不到子组件ref”、“为什么在onShow里操作子组件方法有时会报错”这类问题。通常的准则是:涉及子组件交互的操作,最好放在页面的onReady之后,或者子组件的mounted生命周期中。
5. 实战中的“坑”与高级应用场景
知道了生命周期函数有哪些和它们的顺序,只是第一步。在实际项目中,灵活、正确地运用它们才能避免踩坑,并实现高级功能。
5.1 场景一:列表页与详情页的数据同步
这是一个经典场景。从列表页进入详情页编辑某项数据后,返回列表页,列表需要刷新以显示最新数据。
错误做法:在列表页的onLoad中加载数据。因为返回时onLoad不会执行,列表不会刷新。
正确做法:在列表页的onShow生命周期中加载或刷新数据。
// list-page.vue export default { data() { return { list: [] }; }, onShow() { // 每次页面显示时(包括从详情页返回),都重新加载数据 this.loadListData(); }, methods: { async loadListData() { this.list = await api.fetchList(); }, navigateToDetail(id) { uni.navigateTo({ url: `/pages/detail/detail?id=${id}` }); } } };优化:如果列表数据量大,频繁刷新可能影响体验。可以引入一个“脏标记”(needRefresh),只在从详情页返回时才刷新。这可以通过在跳转时传递标记,或在全局状态中管理来实现。
5.2 场景二:处理视频/音频的播放与后台暂停
在播放页,当用户切到后台或跳转到其他页面时,应暂停播放以节省资源和提供更好体验。
// play-page.vue export default { data() { return { videoContext: null }; }, onLoad() { // 创建视频上下文 this.videoContext = uni.createVideoContext('myVideo'); }, onShow() { // 页面显示时,如果是来自后台切换,可以考虑自动播放(需结合业务逻辑) // this.videoContext.play(); }, onHide() { // 页面隐藏时,暂停播放 if (this.videoContext) { this.videoContext.pause(); } }, onUnload() { // 页面卸载时,彻底释放资源 this.videoContext = null; } };注意:对于音频,使用uni.createInnerAudioContext,逻辑类似。但要注意,部分安卓系统在应用退到后台后可能会强制停止音频播放,这是系统行为。
5.3 场景三:条件编译与生命周期
UniApp的条件编译#ifdef可以用于代码块,但它无法直接作用于生命周期函数声明。如果你需要在不同平台执行不同的生命周期逻辑,可以这样做:
export default { onLoad(options) { // 所有平台共有的逻辑 console.log('页面加载', options); // 平台特定逻辑 #ifdef APP-PLUS this.handleAppLaunchParams(options); #endif #ifdef MP-WEIXIN this.handleMpLaunchParams(options); #endif }, onShow() { #ifdef H5 // 仅在H5平台执行的逻辑,比如处理H5路由的特定参数 this.handleH5RouteQuery(); #endif }, methods: { // 将不同平台的逻辑封装成方法 handleAppLaunchParams(options) { /* ... */ }, handleMpLaunchParams(options) { /* ... */ } } };5.4 场景四:onBackPress的异步陷阱
onBackPress的返回值应该是同步的boolean类型。但实际业务中,我们经常需要弹出模态框让用户确认,这是一个异步操作。
错误示范:
onBackPress() { uni.showModal({ title: '提示', content: '确定离开?', success: (res) => { if (res.confirm) { return false; // 这个return无法被onBackPress捕获! } else { return true; // 同上,无效! } } }); // 默认返回false,无法达到拦截目的 }showModal的success回调是异步的,其返回值是回调函数的返回值,并不是onBackPress函数的返回值。onBackPress在调用showModal后会立即执行完毕,默认返回false,导致拦截失败。
正确做法:利用showModal的异步特性,在回调中不返回值,而是通过其他方式控制导航,但更常见的做法是让onBackPress同步返回true来先拦截,然后在异步回调中手动控制是否执行返回。
onBackPress() { if (this.needConfirm) { this.showConfirmModal(); // 弹出模态框 return true; // 先拦截返回行为 } return false; }, methods: { showConfirmModal() { uni.showModal({ title: '提示', content: '内容未保存,确定退出?', success: (res) => { if (res.confirm) { // 用户确认退出,使用uni.navigateBack()手动返回 uni.navigateBack(); } // 如果取消,什么都不做,页面保持,拦截状态自然解除(但注意,下次按返回键会再次触发) } }); } }注意:这种方式在部分安卓物理返回键场景下可能需要结合plus.key.addEventListener进行更细致的处理。
5.5 场景五:Skyline渲染引擎下的生命周期差异
当在微信小程序中开启Skyline渲染引擎(在app.json中配置"renderer": "skyline")后,生命周期和行为会有一些变化,需要特别注意:
- 初始渲染顺序可能不同:Skyline为了更快的首屏渲染,可能会优化组件和页面的初始化顺序。这要求你的代码不能对传统的生命周期顺序有强依赖。例如,在
onReady中操作子组件应该仍然是安全的,但理论上子组件的mounted可能更早或更晚。 onPageScroll等事件:在Skyline下,滚动事件的触发频率和细节可能有所不同,需要做好兼容性测试。- 自定义组件通信:Skyline使用了不同的底层渲染机制,父子组件间通过
selectComponent等方式获取实例的行为需要验证。
建议:如果计划使用Skyline,应在开发中期就开启进行兼容性测试,而不是等到项目尾声。重点关注与视图渲染、组件交互相关的生命周期函数和API。
6. 性能优化与生命周期的最佳实践
合理地利用生命周期,是进行性能优化的有效手段。
6.1 数据请求的时机选择
- 首屏关键数据:放在页面的
onLoad中。这是最早可以发起请求的时机,能最大程度减少用户等待时间。 - 非关键或实时性要求高的数据:可以放在
onShow中,确保每次页面可见时都是最新数据。但要注意可能造成的频繁请求,可适当加入防抖或条件判断。 - 避免在
created中请求阻塞渲染的数据:虽然created里可以发起请求,但如果请求数据是渲染所必须的,放在onLoad语义更清晰。如果请求不阻塞渲染,放在created或onLoad区别不大。 - 利用
onReady进行非阻塞的后续加载:对于首屏不需要立即展示,但很快会用到的数据(如第二屏的内容、推荐列表等),可以在onReady中发起请求,这样不会影响首屏渲染速度。
6.2 事件监听与内存管理
这是一个极易导致内存泄漏的领域。
原则:在哪里绑定,就在哪里解绑。
- 全局事件总线:如果在页面中使用
uni.$on监听全局事件,必须在onUnload中使用uni.$off进行解绑。否则,即使页面销毁,回调函数仍然被引用,无法被垃圾回收。onLoad() { uni.$on('userInfoUpdated', this.handleUserInfoUpdate); }, onUnload() { uni.$off('userInfoUpdated', this.handleUserInfoUpdate); } - 定时器:在
onHide或onUnload中必须清除。推荐在onHide中清除,在onShow中重新创建,以提供更好的后台体验。 - 第三方库实例:某些图表库、地图库会创建全局的实例或监听器。查阅其文档,确保在
onUnload中有正确的销毁方法(如dispose)。
6.3 页面栈管理与状态保持
UniApp的页面栈管理因平台而异(小程序有明确的5层或10层限制,H5和App则宽松)。生命周期与此紧密相关。
navigateTo:保留当前页面,跳转新页面。当前页面触发onHide,新页面触发onLoad,onShow等。redirectTo:关闭当前页面,跳转新页面。当前页面触发onUnload,新页面触发onLoad,onShow。navigateBack:关闭当前页面,返回上一页或多级。当前页面触发onUnload,返回的页面触发onShow(注意,不是onLoad)。
状态保持问题:当从详情页返回列表页时,我们通常希望列表页保持之前的滚动位置和搜索状态。这可以通过以下方式实现:
- 不重新加载页面:使用
navigateTo和navigateBack,利用页面栈机制自然保持状态。 - 使用全局状态管理:将列表的过滤条件、分页参数、甚至部分数据存储在Vuex或Pinia中。这样即使页面销毁重建,也能恢复状态。
- 小程序独有:
getCurrentPages():在小程序中,可以通过这个API获取页面栈实例,直接操作上一个页面的数据和方法(需谨慎使用,耦合性高)。
6.4 一个综合案例:带搜索、分页、过滤的复杂列表页
让我们设计一个产品列表页的生命周期策略:
onLoad(options):- 接收可能从外部传入的初始搜索关键词(
options.keyword)。 - 初始化页面内部状态:
keyword(搜索词),filter(筛选条件),page(当前页码),list(数据列表),loading(加载状态)。 - 根据
options设置初始的keyword和filter。
- 接收可能从外部传入的初始搜索关键词(
onShow():- 检查全局状态中是否有标记需要刷新(例如,从筛选页返回并应用了新的筛选条件)。如果有,则重置
page=1并调用loadData()。 - 如果没有任何需要刷新的标记,且列表已有数据(
list.length > 0),则可以选择不自动刷新,以保持用户体验流畅。
- 检查全局状态中是否有标记需要刷新(例如,从筛选页返回并应用了新的筛选条件)。如果有,则重置
onReady():- 可以在这里执行一些与DOM相关的初始化,比如绑定滚动监听(用于上拉加载更多),但注意
onPageScroll本身是生命周期,无需手动绑定。
- 可以在这里执行一些与DOM相关的初始化,比如绑定滚动监听(用于上拉加载更多),但注意
onHide():- 清除当前可能正在进行的网络请求(如果使用了可取消的请求库如axios)。
- 停止任何页面内的动画或轮播。
onUnload():- 移除可能绑定的全局事件监听器(如一个全局的“清空搜索”事件)。
- 清理
onPageScroll监听器中可能存在的闭包引用。
onReachBottom():- 触发加载更多。先判断是否正在加载或已无更多数据,然后
page++并调用loadData(true)(传入一个isLoadMore标志)。
- 触发加载更多。先判断是否正在加载或已无更多数据,然后
onPullDownRefresh():- 触发刷新。重置
page=1,调用loadData(),并在请求完成后调用uni.stopPullDownRefresh()。
- 触发刷新。重置
loadData(isLoadMore = false)方法:- 组合
keyword,filter,page参数发起请求。 - 如果是
isLoadMore,则将新数据追加到list;否则,替换list。 - 处理加载状态和错误状态。
- 组合
通过这样精细化的生命周期管理,这个列表页就能在各种交互场景下(首次进入、搜索、筛选、下拉刷新、上拉加载、从子页面返回)都表现出正确的行为,同时保持良好的性能和用户体验。
理解并熟练运用UniApp的生命周期,就像是拿到了应用程序的时序电路图。它不能直接解决所有业务问题,但能让你清晰地知道代码在何时、以何种顺序执行,从而避免状态错乱、资源泄漏和性能瓶颈。下次当你遇到页面渲染问题、数据不同步或者奇怪的Bug时,不妨先静下心来,画一画生命周期的执行顺序图,很可能答案就隐藏在其中。