uni-app原生tabBar无法缓存页面状态,因其由宿主环境独立渲染,切换时页面实例被卸载,data和ref全部丢失;必须改用custom-tabBar配合二维Map缓存关键状态(如scrollY、list),并在onHide/onShow中手动存取,避免白屏与重复请求。

uni-app 本身不提供“多 Tab 页面缓存”这个功能,所谓“缓存 Tab 页面”,其实是绕过原生 tabBar 的限制,用自定义组件 + 状态管理 + 条件渲染模拟出来的行为。直接改 pages.json 里的 tabBar 配置,或指望 onUnload 自动触发清理,都会失败。
为什么原生 tabBar 无法缓存页面状态
uni-app 原生 tabBar 是由微信/支付宝/APP 宿主环境直接渲染的,每个 tab 对应一个独立页面实例,切换时旧页面会 被卸载(destroy),data、computed、ref 全部丢失。你看到的“返回后还在原位置”,只是 WebView 缓存了滚动条或 input 光标,不是 Vue 实例在运行。
- tabBar 页面之间无法共享 Vue 实例,
keep-alive对它们完全无效 -
onHide/onShow会触发,但onUnload只在真正退出栈时才执行(比如从首页跳进详情页再 back) - 即使你在
onShow里重拉数据,列表滚动位置、表单输入、Picker 展开状态等依然丢失
custom-tabBar + 二维缓存 Map 是唯一可行路径
必须放弃原生 tabBar,改用自定义 tabBar 组件,并为每个 tab 维护一份独立的“页面快照”。核心是用一个二维结构(如 Map)存住每个 tab 下的组件状态、滚动位置、列表数据等。
- 第一维 key 是 tab 标识(如
'home'、'cart'),第二维 key 是具体状态字段(如'scrollY'、'searchKeyword') - 在
onHide时手动保存关键状态:this.cache.set(tabId, { scrollY: this.$refs.list?.scrollTop || 0, list: this.list }) - 在
onShow时恢复:const cached = this.cache.get(tabId); if (cached) this.list = cached.list - 避免直接把整个
this.$data深拷贝进去——性能差、易循环引用,只存必要字段
tab-item 切换时如何避免重复请求和白屏
单纯缓存状态还不够,切换 tab 时如果每次都走 onLoad → 请求 → 渲染,用户会感知到明显延迟甚至白屏。得控制加载时机。
- 首次进入某个 tab 时才触发真实请求;后续切换只做状态还原,不重复调
uni.request - 用
v-if控制 tab 内容是否挂载(而非v-show),防止未激活 tab 的 watch/computed 持续运行 - 预加载策略要谨慎:可在登录成功后,用
setTimeout延迟 300ms 主动触发非当前 tab 的首次数据拉取,但别在onLaunch就全量预热——内存和流量都扛不住 - 注意 H5 端路由守卫不生效,
beforeRouteEnter在 tabBar 场景下基本没用,一切以onShow/onHide为准
容易被忽略的坑:滚动位置还原和 ref 失效
很多人缓存了 scrollTop,却忘了 DOM 节点可能还没渲染完,this.$refs.list?.scrollTop = cached.scrollY 直接报错或无效。
- 必须等 DOM 更新完成后再赋值,用
this.$nextTick(() => { this.$refs.list.scrollTop = y }) - 如果用了
virtual-list或uni-list这类封装组件,它们内部滚动容器不是$refs.list,得查文档找真实 scroll 元素(比如$refs.list?.$el?.children[0]) - 缓存的不只是数字,还有
input的 focus 状态、picker的选中值、map的中心点坐标——这些都不能靠 Vue 响应式自动同步 - App 端
plus.webview有独立生命周期,若混用 webview API,缓存逻辑要额外适配










