ARTICLE · 1033686
uni-app避坑指南:5个90%开发者都踩过的坑
uni-app「一套代码,多端运行」确实香,但真正上手后你会发现:所谓跨端,跨的是语法,跨不了平台差异。这篇文章把我带团队做 uni-app 三年、大大小小十几个项目里最常踩的 5 个坑整理出来,每一个都给出错误写法、正确写法和背后的原因。建议先收藏,再慢慢看。
坑 1:条件编译写错位置,代码「诡异失效」
条件编译是 uni-app 跨端的灵魂,但它有个极易被忽视的特性:它是注释驱动的,必须写在注释里。很多从 Vue 转过来的同学会把它当成普通指令写,结果在 H5 上跑得好好的,一打包小程序就「神秘消失」。

条件编译发生在编译期,不符合平台的代码块会被整体剔除
❌ 错误写法:手动判断平台,容易漏端
// 想只在 App 端隐藏某个按钮<view v-if="!isApp">导出数据</view>// 手动判断平台,忘记覆盖所有端const isApp = uni.getSystemInfoSync().platform === 'ios' || uni.getSystemInfoSync().platform === 'android'
✅ 正确写法:交给编译器
<!-- #ifdef APP-PLUS --><button>导出数据</button><!-- #endif -->// JS 中同理,注意是写在注释里// #ifdef H5console.log('这段只在 H5 执行')// #endif
💡 实用技巧:#ifndef H5 表示「除了 H5 以外的所有端」,往往比逐个列举更不容易漏。团队内建议统一约定:能用 ifndef 就用 ifndef。
坑 2:rpx 用在所有地方,宽屏上布局直接爆炸
rpx 的规则是「750rpx = 屏幕宽度」,在手机上很完美。但问题来了:当你在 iPad、PC 浏览器上打开 H5 时,屏幕宽度可能是 1200px 甚至更多,此时 1rpx 会被等比放大,一个按钮能宽得像条横幅。

rpx 等比放大:屏幕越宽,元素越大,宽屏下布局会失去控制
✅ 解法:rpx 只用于「随屏幕缩放」的部分
/* 容器、卡片留白等跟随屏幕缩放:放心用 rpx */.card { padding: 24rpx; border-radius: 16rpx; }/* 按钮、弹窗、内容区最大宽度:用 px + max-width 锁死 */.btn { width: 600rpx; max-width: 320px; /* 宽屏下不再放大 */ margin: 0 auto;}/* page 级别也可以限制内容宽度 *//* #ifdef H5 */.page-content { max-width: 720px; margin: 0 auto; }/* #endif */
⚠️ 重点提醒:uni-app 官方在 H5 端默认开启了 rpx 最大宽度限制(通常 960px),但这个值可以在 manifest.json 中修改。接手老项目时,先检查这个配置,别被前人埋的雷炸到。
坑 3:onLoad、onShow、created 分不清,数据重复加载
uni-app 页面的生命周期比普通 Vue 页面多了一层「页面生命周期」,很多同学用惯了 created/mounted,结果发现:小程序里 created 拿不到页面参数;返回上一页时数据没有刷新,或者刷新了两次。

onLoad 只在首次加载执行一次;onShow 每次可见都会触发
✅ 正确的使用姿势
export default { // onLoad:接收页面参数 + 只需执行一次的初始化 onLoad(options) { this.goodsId = options.id this.loadDetail() }, // onShow:需要「每次回来都最新」的数据 onShow() { this.refreshCartCount() // 购物车角标、未读消息数等 }, methods: { async loadDetail() { /* ... */ } }}
💡 经典 bug:把「列表数据加载」写在 onShow 里,用户从详情页返回时整个列表闪一下重新加载。正确做法是首屏数据放 onLoad,只有「可能被上一页修改过」的状态(如收藏、点赞数)才放 onShow。
坑 4:uni.setStorage 的同步异步之争
uni-app 的存储 API 有 Sync 和非 Sync 两套,很多人无脑用同步版,图省事。同步 API 的问题是:它会阻塞 JS 线程。数据量小的时候无感,一旦往 storage 里塞了几 MB 的缓存数据,页面会明显卡顿。
✅ 推荐实践:封装一层,业务层无感知
// utils/storage.jsconst PREFIX = 'myapp_'export const storage = { async get(key, defaultValue = null) { try { const val = uni.getStorageSync(PREFIX + key) return val === '' ? defaultValue : val } catch (e) { console.error('storage get error:', e) return defaultValue } }, async set(key, value) { try { uni.setStorageSync(PREFIX + key, value) return true } catch (e) { // 10MB 上限:写入失败通常是空间不足 if (e.errMsg && e.errMsg.includes('exceed')) { uni.clearStorageSync() // 按业务改成 LRU 清理更佳 return uni.setStorageSync(PREFIX + key, value) } return false } }, remove(key) { uni.removeStorageSync(PREFIX + key) }}// 业务代码统一调用,将来换方案只需改这一处await storage.set('userInfo', { name: '张三' })
💡 两个小知识点:① storage 存的都是字符串,uni-app 的 Sync API 已自动处理对象,但读出来要做类型校验;② 小程序端 storage 总上限 10MB,别把图片 base64 塞进去。
坑 5:scroll-view 与页面滚动打架,下拉刷新失灵
「我的页面滚动不了了!」「下拉刷新怎么不触发?」——这几乎每个 uni-app 新手都会遇到。根因是要先想清楚:你的滚动到底发生在哪一层?
用页面级滚动(默认):内容超出屏幕时页面自然滚动,支持下拉刷新 onPullDownRefresh;
用 scroll-view 固定高度滚动:页面本身不滚了,onPullDownRefresh 失效,要用 scroll-view 自己的 refresher-enabled。
❌ 典型错误:height: 100% 不生效
<scroll-view scroll-y style="height: 100%"> /* 无效! */ <view v-for="item in list">...</view></scroll-view>
✅ 正确写法:给 scroll-view 一个确定高度
/* 方案一:css 撑满视口 */page { height: 100%; }.container { height: 100vh; display: flex; flex-direction: column;}.scroll-area { flex: 1; overflow: hidden; }/* 方案二(推荐):scroll-view 用 flex 剩余空间 */<scroll-view scroll-y class="scroll-area" refresher-enabled :refresher-triggered="isRefreshing" @refresherrefresh="onRefresh"> <view v-for="item in list" :key="item.id" class="item"> {{ item.title }} </view></scroll-view>async onRefresh() { this.isRefreshing = true await this.loadData() this.isRefreshing = false uni.stopPullDownRefresh()}
⚠️ 记住一条铁律:scroll-view 必须有确定的固定高度(vh / flex 计算结果均可),否则它会被内容撑开,滚动条永远不会出现。
写在最后
跨端框架的本质是「用约定换效率」:你遵守框架的平台约定,框架帮你抹平 80% 的差异,剩下 20% 就靠条件编译和踩坑经验补齐。这 5 个坑覆盖了我见过 90% 的线上事故,建议:
把条件编译的规范写进团队代码评审 checklist;
封装 storage、request 等基础层,业务代码不直接调 uni API;
每个端都真机跑一遍,别只盯微信开发者工具。
下一篇我会带来完整实战案例:从零开发一个多端待办清单应用,数据持久化、组件拆分、跨端细节全部展示,代码可直接跑。关注不迷路。