会,卡顿是架构层面的必然结果:自定义导航栏为vue组件,渲染路径长、通信开销大、无gpu加速;原生tabbar由客户端直接接管,不走js层,无setdata和vnode diff。

会,而且卡顿不是偶发问题,是架构层面的必然结果——自定义导航栏(包括顶部导航栏和底部 tabbar)在 uni-app 中本质是 Vue 组件,渲染路径长、通信开销大、无法享受原生层 GPU 加速,真机上尤其明显。
为什么自定义导航栏比原生 tabbar 更容易卡顿
根本原因不在代码写得“好不好”,而在运行时位置不同:
- 原生
tabBar(pages.json中配置"custom": false)由微信客户端直接接管,不走 JS 层,无 setData、无 VNode diff、无 CSS 重排重绘 - 自定义导航栏是页面内一个
<view></view>或组件,每次切换、响应式更新、甚至scroll-view滚动都可能触发它的重新计算与渲染 - uni-app 的 Vue 2 响应式系统对数组/对象深层变更敏感,比如动态修改
tabList数组,会触发整条链路的依赖追踪和 patch - 低端安卓机或 iOS 微信旧版本中,
createSelectorQuery+boundingClientRect在滚动中频繁调用,CPU 直接飙高
常见卡顿场景与对应 fix
不是所有自定义导航栏都一样卡,以下几种写法最危险:
- 在
onPullDownRefresh或onReachBottom里直接改this.navList = [...]—— 应该用Object.assign({}, ...)或JSON.parse(JSON.stringify(...))避免响应式污染 - 把整个导航栏封装成子组件,并在父页用
v-if控制显隐 —— 切换时组件销毁/重建开销大,改用v-show+opacity: 0更轻量 - 导航栏内用了
watch监听路由或状态变化,且未加防抖 —— 改为onTabItemTap生命周期钩子 +throttle包裹 - 图标用
<image></image>渲染但没设width/height或mode="widthFix"—— 导致 layout thrashing,图片加载完才重排布局
pages.json 里启用 "custom": true 后还必须做三件事
很多人以为只要配了 "custom": true 就万事大吉,其实这只是第一步,漏掉下面任意一项都会放大卡顿:
- 必须在每个 tab 页面的
<page></page>根节点上加style="padding-bottom: env(safe-area-inset-bottom)",否则 iPhone 底部安全区错位,强制重绘 - 自定义 tabbar 组件不能放在
<scroll-view></scroll-view>内部,否则滚动时它会随内容一起被 transform,触发额外合成层 - 禁止在导航栏组件里调用
uni.createSelectorQuery().select(...).boundingClientRect()做尺寸测量 —— 改用getComputedStyle(H5)或提前缓存uni.getSystemInfoSync().screenHeight计算固定高度
真正难优化的不是“怎么让它动起来”,而是“怎么让它不动”——减少重绘触发点、剥离响应式依赖、绕过 setData 通信瓶颈。很多项目卡顿到最后,其实只是把 this.tabIndex = 1 换成了 this.$nextTick(() => { this.tabIndex = 1 }),就稳了。











