Appearance
uniapp 高频面试题
UniApp 高频面试题(含答案 + 亮点,前端面试必背)
一、基础概念类
1. 什么是 uni-app?它的核心优势是什么?
uni-app 是使用 Vue 语法开发,一套代码多端发布的跨平台框架。
可编译到:微信 / 支付宝 / 百度 / 抖音小程序、H5、App(iOS/Android)、快应用。
核心优势:
- 一套代码运行多端,大幅降低开发、维护成本
- 完全兼容 Vue 语法,Vue 开发者上手零成本
- 原生渲染性能高于 WebView,体验接近原生 App
- 生态丰富,插件市场成熟,组件 / API 齐全
- 支持条件编译,灵活区分不同平台差异化逻辑2. uni-app 和原生小程序、Vue H5 的区别?
对比原生小程序:
- 语法:vue 语法替代小程序原生 wxml/wxss
- 工程化:支持 npm、webpack、vuex,工程化能力更强
- 跨端:一套代码多端,原生小程序只能单端
对比 Vue H5:
- 渲染:App / 小程序端是原生渲染,不是浏览器 H5 渲染,性能更好
- 能力:拥有原生 App 能力(推送、蓝牙、离线打包、原生插件)
- 标签:uni 内置跨端组件,抹平各平台标签差异3. uni-app 页面生命周期有哪些?执行顺序?
页面生命周期(常用):
onLoad > onShow > onReady > onHide > onUnload
- onLoad:页面加载,只执行一次,可接收页面传参
- onShow:页面显示,每次进入页面都会执行
- onReady:页面初次渲染完成,DOM 节点就绪
- onHide:页面隐藏(跳转页面、弹窗遮盖)
- onUnload:页面卸载,页面销毁
补充:App 特有 onLaunch(应用初始化,全局仅一次)4. 组件生命周期和页面生命周期区别?
- 组件用 Vue 原生生命周期:created、mounted、updated、destroyed
- 页面用 uni-app 专属生命周期:onLoad/onShow/onReady
重点:组件没有 onLoad,不要在组件里写 onLoad!二、路由与页面跳转类
1. uni-app 路由跳转方式及区别?
- uni.navigateTo:保留当前页,跳转到新页面,有返回按钮
- uni.redirectTo:关闭当前页,跳转新页,无返回
- uni.reLaunch:关闭所有页面,重启到目标页,清空页面栈
- uni.switchTab:跳转到 tabBar 页面,关闭其他非 tab 页面
- uni.navigateBack:返回上一页 / 多级页面,可自定义返回层数2. 页面栈了解吗?最大页面栈限制?
页面栈:uni-app 页面跳转的历史页面记录
限制:
- 小程序端页面栈最多 10 层,超过必须用 reLaunch 或 redirectTo 清空
- 频繁跳转不销毁页面会导致内存卡顿,需要合理销毁页面3. 页面传参有哪些方式?
- URL 拼接传参(简单参数)
- eventChannel 事件通道(复杂对象、长文本)
- 全局变量 globalData
- Vuex / Pinia 状态管理
- 本地存储 storage4. switchTab 为什么不触发 onUnload?
switchTab 只是切换 tab 显示隐藏,不会销毁页面栈,所以不走 onUnload。
解决方案:
- 在 onHide 里做必要的状态清理,不要依赖 onUnload5. getCurrentPages 用途 & 注意事项
用途:
- 获取当前页面栈实例、页面参数
- 可实现跨页赋值、跨页调用方法
注意事项:
- 禁止在 onShow 生命周期过早使用,容易获取不到三、样式与适配类
1. uni-app 尺寸单位 rpx 原理?
rpx:响应式像素,是 uni-app 跨端适配核心单位
原理:
- 规定:750rpx = 屏幕宽度(设计稿统一按 750px 标注)
- 框架自动根据设备屏幕宽度换算 px,实现自适应所有机型
- H5/App/小程序自动换算,一套样式多端适配2. rpx、px、upx、em、rem 区别?
- rpx:自动适配屏幕,推荐做移动端布局首选
- px:固定像素,多端适配差
- upx:uni 旧版单位,已废弃,统一用 rpx
- rem:根字体适配,H5 常用
- em:相对于父元素字体大小3. uni-app 样式隔离 / 组件 scoped 注意点
- 小程序端 scoped 通过 data-v 唯一标识隔离样式
- 深度修改子组件 / 三方组件样式需要用 ::v-deep
- 微信小程序自定义组件样式隔离默认隔离,穿透必须深度选择器4. 各平台样式穿透写法
- Vue / H5:/deep/、::v-deep
- 小程序:::v-deep 稳定兼容
- App 端两种都可用5. uni-app 为什么不推荐 * 全局选择器?
小程序端全局 * 选择器消耗性能极高,会加重视图层渲染压力。四、条件编译(面试高频)
1. 什么是条件编译?常用平台标识符?
概念:同一套代码,通过注释标记,不同平台编译不同代码,实现平台差异化。
常用标识符:
- #ifdef H5:仅 H5 生效
- #ifdef APP-PLUS:App 端生效
- #ifdef MP-WEIXIN:微信小程序
- #ifndef:除了某平台外都生效2. 条件编译可以用在哪些地方?
支持位置:
- js 逻辑
- css 样式
- template 模板
- json 配置(pages.json/manifest.json)
进阶用法:
// 多个平台
#ifdef MP-WEIXIN || MP-ALIPAY
// 除了H5以外所有平台
#ifndef H5
注意:必须是注释写法,空格、格式错会编译失效。五、数据缓存与网络请求
1. uni-app 本地存储方案有哪些?区别?
- uni.setStorageSync:同步存储
- uni.setStorage:异步存储
区别:
- 同步代码简单,适合少量数据
- 异步不阻塞主线程,性能更好,推荐大量数据使用
注意:小程序有本地存储大小限制(10M),大文件不要存在 storage。2. uni.request 封装思路?如何统一拦截请求 / 响应?
封装要点:
- 统一基准域名 baseUrl
- 请求拦截:添加 token、请求头、loading 加载
- 响应拦截:统一处理状态码、错误提示、登录过期跳转
- 封装 Promise 风格,避免回调地狱3. uni.request 原生没有拦截器,怎么实现?
封装思路:
统一封装 request 方法,在内部做:
- 请求拦截:加 token、统一 header、开启 loading
- 响应拦截:统一错误码、登录过期、提示文案4. 小程序域名白盒问题
小程序必须配置 request 合法域名,开发阶段可勾选:
「不校验合法域名、web-view、TLS」六、组件化 & 全局配置
1. 如何全局注册组件?
方式一:在 main.js 中 Vue.component() 全局注册
方式二:pages.json 的 easycom 自动按需引入组件(最常用)
easycom 优势:无需手动 import,自动匹配组件目录,开箱即用。2. easycom 原理和配置?
原理:自动扫描 components 目录下组件,符合命名规范直接使用,无需引入注册。
自定义配置:在 pages.json -> easycom 配置。3. easycom 自动引入原理
- 自动扫描 components/组件名/组件名.vue 目录规范
- 页面中直接使用标签,无需 import、无需注册
- 打包时按需引入,减小体积4. easycom 自定义配置 & 失效常见原因
失效原因:
- 文件夹命名不规范
- 组件名和标签名大小写不统一
- 未重启项目、缓存未清
解决:自定义匹配规则在 pages.json -> easycom 配置。七、生命周期深挖(高频进阶)
1. onLoad、onShow、created、mounted 执行顺序?
完整顺序:
onLaunch(App) → created(页面) → onLoad → onShow → mounted → onReady
关键点:
- onLoad 先于 mounted
- 页面销毁只会走 onUnload,不走 destroyed
- 组件只有 Vue 生命周期,没有 onLoad/onShow2. App 全局生命周期有哪些?
- onLaunch:应用首次启动执行一次
- onShow:应用从后台切前台、启动展示
- onHide:应用压后台
- onError:全局异常捕获
- onUniNViewMessage:原生 nview 通信3. 页面多次跳转,onShow 和 onLoad 区别
- onLoad:页面第一次创建只执行一次
- onShow:每次页面被展示都会执行(返回、tab 切换、前台唤醒)八、渲染原理 & 多端底层差异
1. uni-app 三端渲染机制区别
微信 / 支付宝等小程序端:
- 小程序双线程:逻辑层 + 视图层
- 基于小程序原生渲染,受 setData 通信限制
H5 端:
- 完整 Vue + Web 浏览器渲染,和普通 Vue 项目一致
App 端(iOS/Android):
- App-Plus:原生引擎渲染(非 WebView)
- 性能最高,接近原生体验2. 为什么小程序端数据更新慢?setData 原理
原因:
- 逻辑层和视图层分离、跨线程通信
- setData 是序列化传参,频繁大体积数据传输会卡顿
优化:
- 减少单次 setData 数据量
- 合并赋值、局部更新3. App 端 nvue 和 vue 页面区别
vue 页面:
- 多端通用、写法简单
- App 里仍有 Web 渲染影子
nvue 页面:
- 纯原生渲染,高性能
- 长列表、复杂动画首选
nvue 限制:
- 样式写法特殊、不支持部分 css
- 布局默认 flex4. App 端 32 位 / 64 位包区别
- 64 位:性能更好、支持高版本手机、包体略大
- 32 位:兼容老旧低端机型,包体更小
uni-app 打包可同时出双包或分开发布。九、分包加载 + 优化(必问)
1. 小程序分包加载原理 & 配置
目的:减小主包体积,解决主包超 2M 无法上传问题。
主包:首页、tab 页面、公共资源
分包:其他业务页面,按需加载
配置:在 pages.json 中配置 subPackages。2. 小程序分包机制、主包限制
限制:
- 微信小程序主包不得超过 2M,总包不超 20M
- 分包:按业务拆分页面,按需加载,提速首屏
- 分包不能引用其他分包静态资源,只能引用主包3. 独立分包和普通分包区别
普通分包:依赖主包,必须先加载主包
独立分包:不依赖主包,可单独启动,首屏更快4. 分包跳转注意点
- 分包页面不能通过 tabBar 直接访问
- tab 页面必须放在主包里5. 小程序分包后跳转分包页面注意事项?
一、路径规则必须正确
- 分包页面路径必须写完整路径,不能写相对路径
- 路径必须和 pages.json 里分包配置的 root + pages 一致
示例:
// pages.json
"subPackages": [{
"root": "packageA",
"pages": [{"path": "list/list"}]
}]
// 正确跳转
uni.navigateTo({ url: "/packageA/list/list" });
二、跳转方式限制
- 不能用 uni.switchTab 直接跳转到分包页面
- switchTab 只能跳 tabBar 页面(必须在主包)
- 分包页面之间可以互相跳转,但要注意路径正确
- 主包页面可以直接跳分包页面,分包页面也可以跳主包页面
三、资源依赖限制
- 分包页面不能引用其他分包的静态资源(如图片、js、css)
- 只能引用主包或自身分包内的资源
- 分包可以引用主包的公共资源(如组件、工具类)
- 主包不能引用分包的资源
四、独立分包特殊注意事项
- 独立分包不依赖主包,可以单独启动
- 跳转时不需要先加载主包
- 独立分包不能引用主包的资源,必须自带所有依赖6. 如何解决小程序分包主包体积过大?
一、减少主包体积(优先做)
1. 清理无用资源
- 删除未使用的图片、组件、工具类文件
- 压缩主包内的图片(用 tinypng 压缩,优先用 webp 格式)
- 精简第三方依赖,移除未使用的 npm 包
2. 优化静态资源
- 主包内的大图片、视频等资源上传 CDN,改为网络地址引用
- 避免在主包引入全量 UI 库,改用按需引入(如 uView 按需引入组件)
3. 精简代码
- 移除主包内未使用的页面、组件、工具函数
二、合理分包
1. 按业务拆分
- 把非核心业务页面拆到分包
- 独立功能模块(如个人中心、设置、活动页)独立分包
2. 分包预加载
- 在 pages.json 配置 preloadRule,提前加载可能访问的分包
3. 独立分包
- 启动页独立分包,不依赖主包,首屏更快
三、优化公共依赖
- 公共组件、工具类放在主包,避免各分包重复打包
- 大型第三方库按需引入或拆到分包十、全局状态、通信方式
1. uni-app 组件 / 页面通信所有方式
- 父子:props / $emit
- 跨页面 / 兄弟:uni.$on / uni.$emit 全局事件
- 全局变量:globalData
- Vuex / Pinia
- 本地缓存 storage
- 页面栈 getCurrentPages 直接拿实例通信2. globalData 和 Vuex 区别
globalData:
- 简单粗暴、无响应式
- 适合简单全局变量
Vuex:
- 响应式、模块化
- 适合复杂状态管理十一、App 端 & 小程序端坑点(实战面试题)
1. uni-app 打包 App 原理?
- App 端基于 weex + 原生引擎渲染,不是 webview
- 支持云端打包、本地离线打包
- 可引入原生 Android/iOS 插件扩展原生能力2. App 离线打包和云端打包区别?
对比项 云端打包 离线打包
实现方式 uni-app 官方服务器在线编译打包 本地搭建 HBuilderX 离线打包环境编译
依赖网络 必须联网,依赖官方打包服务器 可完全离线操作
环境要求 无需本地配置环境,HBuilderX 直接操作 需配置 Android SDK、JDK、Xcode 等原生环境
插件支持 支持官方插件市场的插件 支持任意自定义原生插件,无需审核
包体大小 一般为通用包,体积略大 可按需裁剪,包体更小
更新速度 受服务器排队影响,高峰期慢 本地编译,速度取决于电脑性能
适用场景 快速测试、普通发布、无自定义插件 自定义插件开发、私有化部署、批量打包
核心区别:
- 云端打包适合大多数中小项目,操作简单,不用管原生环境配置
- 离线打包适合有自定义原生插件、需要频繁打包或对包体有严格控制的项目3. uni-app 跨端遇到过哪些兼容坑?怎么解决的?
一、样式兼容(最常见)
坑:rpx 在部分安卓机型上换算异常、小程序端不支持部分 CSS 属性(如 * 选择器、复杂选择器)、App 端 nvue 和 vue 样式写法差异。
解决:
- 统一用 rpx 做布局,避免在 nvue 里写复杂 CSS
- 用条件编译区分平台写样式
- 小程序端避免全局 * 选择器,改用类名限定
二、API 兼容
坑:部分 API 仅特定平台支持(如微信小程序的 wx.getUpdateManager、App 端的 plus API)、H5 端部分小程序 API 不兼容。
解决:
- 用条件编译 #ifdef MP-WEIXIN 包裹平台专属 API
- 封装统一方法,对不支持的平台做降级处理(如 H5 端用浏览器 API 替代)
三、组件兼容
坑:第三方 UI 库(如 uView、Vant)在不同平台样式 / 功能不一致、小程序端自定义组件 scoped 穿透问题。
解决:
- 优先选择跨端兼容性好的组件库
- 小程序端样式穿透用 ::v-deep
- 对平台专属组件做单独适配
四、路由与页面栈
坑:小程序页面栈最多 10 层,频繁跳转导致无法继续跳转;switchTab 不触发 onUnload,导致页面状态未清理。
解决:
- 超过 10 层时用 uni.reLaunch 或 uni.redirectTo 清空页面栈
- 在 onHide 里做必要的状态清理,不要依赖 onUnload
五、打包与上线
坑:微信小程序主包体积超 2M 无法上传、App 端离线打包白屏 / 插件不兼容。
解决:
- 用分包加载拆分小程序主包
- App 端优先用云端打包,离线打包时注意原生插件版本匹配4. 微信小程序下拉刷新不生效?
原因:
- pages.json 未开启 enablePullDownRefresh
- 页面样式滚动异常、外层高度塌陷
- 自定义导航栏遮挡导致触发区域失效5. 小程序异步回调地狱怎么解决?
Promise 封装 + async/await 改造原生 api6. uni-app 打包后 App 白屏常见原因
- 静态资源路径绝对 / 相对路径错误
- 异步接口未做容错、初始化代码报错
- 原生插件版本不兼容
- 路由初始化逻辑阻塞十二、性能优化专项(中高级必问)
1. uni-app 常见性能优化方案
- 图片懒加载、图片压缩、使用 webp
- 分包加载,拆分主包体积
- 避免频繁 setData / 频繁赋值
- 列表长列表用 virtual-list 虚拟列表
- 合理销毁页面、清除定时器、解绑监听
- 路由按需加载、组件懒引入2. uni-app 全方位性能优化方案
- 小程序分包、拆分主包体积
- 长列表使用 virtual-list 虚拟列表
- 减少不必要 setData、减少页面频繁赋值
- 图片懒加载、图片压缩、webp
- 销毁定时器、解绑全局事件、页面卸载清理监听
- 合理使用 nvue 做高性能页面
- 路由懒加载、组件按需引入
- 减少全局样式污染、精简全局 css3. 首屏加载慢如何排查?
- 主包体积过大?→ 分包
- 首屏请求太多接口?→ 合并接口、缓存
- 静态资源太大?→ 压缩、cdn
- 首页逻辑太多?→ 拆分延迟执行十三、框架对比
1. uni-app VS Taro VS 原生小程序 对比
uni-app:上手简单、生态强、vue 友好、跨端稳
Taro:配置复杂、自由度高、多框架支持、适合大型团队
原生小程序:性能最优、无跨端损耗、维护成本高2. uni-app 和 Taro、Mpx 对比优缺点?
一、uni-app
优点:Vue 语法上手快、生态最成熟、跨端稳定性高、文档完善、插件市场丰富、HBuilderX 开发体验好。
缺点:跨端深度定制能力弱、大型项目架构支持不如 Taro、部分平台适配依赖官方更新。
二、Taro
优点:支持 React/Vue 多框架、配置灵活、自定义程度高、大型项目架构友好、跨端能力强。
缺点:学习成本高、配置复杂、生态不如 uni-app 成熟、部分场景适配坑多。
三、Mpx
优点:基于小程序原生语法扩展,性能损耗小、原生小程序兼容度高、轻量高效。
缺点:仅支持微信 / 支付宝等小程序端,跨端支持弱、生态小、社区活跃度低。
横向对比结论:
- 快速开发、Vue 技术栈、中小项目:优先选 uni-app
- 大型项目、React 技术栈、需要深度定制:优先选 Taro
- 纯小程序开发、追求极致性能:可以考虑 Mpx十四、手写代码题(面试现场常考)
1. 封装全局 uni.request 请求
javascript
const baseUrl = 'https://api.example.com'
function request(options) {
return new Promise((resolve, reject) => {
uni.showLoading({ title: '加载中' })
uni.request({
url: baseUrl + options.url,
method: options.method || 'GET',
data: options.data || {},
header: {
Authorization: uni.getStorageSync('token') || '',
'Content-Type': 'application/json'
},
success: res => {
uni.hideLoading()
if (res.statusCode === 200) {
resolve(res.data)
} else if (res.statusCode === 401) {
uni.navigateTo({ url: '/pages/login/login' })
reject(new Error('登录过期'))
} else {
uni.showToast({ title: res.data.message || '请求失败', icon: 'none' })
reject(new Error(res.data.message))
}
},
fail: err => {
uni.hideLoading()
uni.showToast({ title: '网络错误', icon: 'none' })
reject(err)
}
})
})
}
export default request2. rpx 自动适配理解 + 通用页面布局
css
/* 通用页面布局 */
.page-container {
width: 750rpx;
min-height: 100vh;
background: #f5f5f5;
}
/* 头部固定 */
.header {
position: fixed;
top: 0;
left: 0;
width: 750rpx;
height: 88rpx;
background: #fff;
z-index: 999;
}
/* 内容区域 */
.content {
padding: 88rpx 30rpx 30rpx;
}
/* 设计稿标注 375px 宽度,直接写 rpx */
.box {
width: 375rpx;
height: 200rpx;
margin: 20rpx;
}3. 条件编译区分微信小程序和 H5
javascript
// #ifdef MP-WEIXIN
console.log('这是微信小程序环境')
wx.getUpdateManager()
// #endif
// #ifdef H5
console.log('这是 H5 环境')
window.location.reload()
// #endifcss
/* #ifdef MP-WEIXIN */
.box {
padding-top: 20rpx;
}
/* #endif */
/* #ifdef H5 */
.box {
padding-top: 10px;
}
/* #endif */html
<!-- #ifdef MP-WEIXIN -->
<view>微信小程序专属内容</view>
<!-- #endif -->
<!-- #ifdef H5 -->
<view>H5 专属内容</view>
<!-- #endif -->4. 全局事件 $on / $emit 跨页面通信 demo
javascript
// 页面 A:监听事件
onLoad() {
uni.$on('updateData', (data) => {
console.log('收到更新:', data)
this.list = data
})
},
onUnload() {
uni.$off('updateData')
}
// 页面 B:触发事件
handleSubmit() {
uni.$emit('updateData', this.newList)
uni.navigateBack()
}5. easycom 自定义组件自动引入配置
json
// pages.json
{
"easycom": {
"autoscan": true,
"custom": {
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
"^custom-(.*)": "@/components/custom/$1.vue"
}
}
}6. 小程序分包 pages.json 完整配置
json
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/user/user",
"style": {
"navigationBarTitleText": "个人中心"
}
}
],
"subPackages": [
{
"root": "packageA",
"pages": [
{
"path": "list/list",
"style": {
"navigationBarTitleText": "列表页"
}
},
{
"path": "detail/detail",
"style": {
"navigationBarTitleText": "详情页"
}
}
]
},
{
"root": "packageB",
"pages": [
{
"path": "setting/setting",
"style": {
"navigationBarTitleText": "设置"
}
}
]
}
],
"preloadRule": {
"pages/index/index": {
"network": "all",
"packages": ["packageA"]
}
}
}十五、兼容性问题汇总
1. 样式兼容问题
1.1 rpx 单位在不同平台换算异常
问题:
- 部分安卓机型 rpx 换算精度问题
- nvue 页面不支持 rpx,只支持 px
解决方案:
- 统一使用 rpx,避免混用单位
- nvue 页面使用 px 或 upx
- 设计稿统一按 750px 标注1.2 CSS 选择器兼容问题
问题:
- 小程序不支持 * 通配符选择器
- 小程序不支持属性选择器 [attr]
- 小程序不支持部分伪类选择器(:not、:nth-child 等)
- nvue 不支持复杂选择器
解决方案:
- 避免使用 * 选择器,改用具体类名
- 使用 class 选择器替代属性选择器
- 简化选择器层级,避免嵌套过深
- nvue 页面使用简单类名选择器1.3 CSS 属性兼容问题
问题:
- 小程序不支持 position: fixed 在 scroll-view 内
- nvue 不支持 background-image 本地路径
- nvue 不支持 box-shadow
- 小程序不支持 filter 滤镜
解决方案:
- fixed 定位元素放在 scroll-view 外层
- nvue 使用网络图片或 base64
- nvue 用边框或图片模拟阴影效果
- 使用图片替代 CSS 滤镜效果1.4 flex 布局兼容问题
问题:
- nvue 默认 flex 布局,display: flex 无效
- 小程序 flex 布局需要写完整前缀
解决方案:
- nvue 直接写 flex 属性,无需 display: flex
- 使用 autoprefixer 自动添加前缀
- 避免使用 flex: 1,改写完整属性2. API 兼容问题
2.1 平台专属 API
问题:
- 微信小程序:wx.getUpdateManager()、wx.openSetting()
- 支付宝小程序:my.getAuthCode()
- App 端:plus.runtime、plus.navigator
- H5 端:部分原生 API 不支持
解决方案:
// 条件编译处理
// #ifdef MP-WEIXIN
wx.getUpdateManager()
// #endif
// #ifdef MP-ALIPAY
my.getAuthCode()
// #endif
// #ifdef APP-PLUS
plus.runtime.version
// #endif
// #ifdef H5
// 使用浏览器 API 替代
// #endif2.2 网络请求兼容
问题:
- 小程序必须配置合法域名
- H5 跨域问题
- App 端无跨域限制
解决方案:
// 小程序:在微信公众平台配置 request 合法域名
// H5:后端配置 CORS 或使用代理
// 开发阶段勾选「不校验合法域名」
// 统一封装
function request(options) {
// #ifdef H5
const baseUrl = '/api' // 代理路径
// #endif
// #ifdef MP-WEIXIN || APP-PLUS
const baseUrl = 'https://api.example.com'
// #endif
return uni.request({ url: baseUrl + options.url, ...options })
}2.3 文件上传下载兼容
问题:
- 小程序单次上传文件大小限制 10M
- H5 文件路径格式不同
- App 端可访问本地文件系统
解决方案:
// 大文件分片上传
async function uploadLargeFile(filePath) {
const chunkSize = 5 * 1024 * 1024 // 5M 分片
// 分片上传逻辑...
}
// 条件编译处理路径
// #ifdef H5
const path = URL.createObjectURL(file)
// #endif
// #ifdef APP-PLUS
const path = plus.io.convertLocalFileSystemURL(localPath)
// #endif2.4 支付功能兼容
问题:
- 微信小程序:wx.requestPayment
- 支付宝小程序:my.tradePay
- App 端:uni.requestPayment 统一封装
- H5 端:需要跳转支付页面
解决方案:
// 统一支付封装
function pay(options) {
// #ifdef MP-WEIXIN
return wx.requestPayment({
timeStamp: options.timeStamp,
nonceStr: options.nonceStr,
package: options.package,
signType: options.signType,
paySign: options.paySign
})
// #endif
// #ifdef MP-ALIPAY
return my.tradePay({ tradeNO: options.tradeNO })
// #endif
// #ifdef APP-PLUS
return uni.requestPayment({
provider: options.provider,
orderInfo: options.orderInfo
})
// #endif
// #ifdef H5
window.location.href = options.payUrl
// #endif
}3. 组件兼容问题
3.1 内置组件差异
问题:
- scroll-view 在不同平台滚动性能差异
- swiper 组件高度计算方式不同
- video 组件层级问题
- map 组件覆盖问题
解决方案:
// scroll-view 性能优化
<scroll-view
scroll-y
:style="{ height: scrollHeight + 'px' }"
@scrolltolower="loadMore"
>
<!-- 内容 -->
</scroll-view>
// swiper 高度自适应
<swiper :style="{ height: swiperHeight + 'px' }">
<swiper-item v-for="item in list">
<!-- 动态计算高度 -->
</swiper-item>
</swiper>
// video/map 层级问题
// 使用 cover-view 覆盖
<video :src="videoSrc">
<cover-view class="controls">覆盖内容</cover-view>
</video>3.2 第三方组件库兼容
问题:
- uView 部分组件在 nvue 不支持
- Vant Weapp 仅支持微信小程序
- 组件样式在不同平台表现不一致
解决方案:
- 优先选择跨端兼容组件库(uView、uni-ui)
- nvue 页面使用原生组件或自定义组件
- 使用条件编译区分平台样式
- 测试各平台组件表现,针对性修复3.3 自定义组件样式穿透
问题:
- 小程序组件样式隔离,无法修改子组件样式
- scoped 样式无法穿透
解决方案:
// 方式一:深度选择器
<style scoped>
.parent ::v-deep .child-class {
color: red;
}
</style>
// 方式二:取消 scoped
<style>
.parent .child-class {
color: red;
}
</style>
// 方式三:组件内开放样式类
// 子组件
Vue.component('child', {
options: { styleIsolation: 'shared' }
})4. 路由与页面栈兼容问题
4.1 页面栈限制
问题:
- 小程序页面栈最多 10 层
- navigateTo 超过限制报错
解决方案:
// 封装安全跳转
function safeNavigate(url, type = 'navigateTo') {
const pages = getCurrentPages()
if (pages.length >= 10) {
// 超过限制,使用重定向
return uni.redirectTo({ url })
}
switch (type) {
case 'navigateTo':
return uni.navigateTo({ url })
case 'redirectTo':
return uni.redirectTo({ url })
case 'reLaunch':
return uni.reLaunch({ url })
}
}4.2 tabBar 页面跳转
问题:
- tabBar 页面只能用 switchTab 跳转
- switchTab 不能传参
解决方案:
// 方式一:全局变量
getApp().globalData.tabParam = data
uni.switchTab({ url: '/pages/index/index' })
// 页面接收
onShow() {
const data = getApp().globalData.tabParam
}
// 方式二:本地存储
uni.setStorageSync('tabParam', data)
uni.switchTab({ url: '/pages/index/index' })4.3 页面返回传参
问题:
- navigateBack 无法直接传参
解决方案:
// 方式一:全局事件
// 当前页面
uni.$emit('updateData', data)
uni.navigateBack()
// 上个页面
onLoad() {
uni.$on('updateData', (data) => {
this.data = data
})
}
onUnload() {
uni.$off('updateData')
}
// 方式二:页面栈直接修改
const pages = getCurrentPages()
const prevPage = pages[pages.length - 2]
prevPage.$vm.data = data
uni.navigateBack()5. 数据存储兼容问题
5.1 存储大小限制
问题:
- 小程序本地存储限制 10M
- H5 localStorage 限制 5M
- 存储数据类型限制
解决方案:
// 小数据用 storage
uni.setStorageSync('key', value)
// 大数据用文件存储
// #ifdef APP-PLUS
plus.io.resolveLocalFileSystemURL(path, (entry) => {
entry.file((file) => {
// 文件操作
})
})
// #endif
// 敏感数据加密存储
function setSecureData(key, value) {
const encrypted = encrypt(value)
uni.setStorageSync(key, encrypted)
}5.2 数据类型转换
问题:
- storage 存储会自动转字符串
- 取出数据需要类型转换
解决方案:
// 封装类型安全存储
function setStorage(key, value) {
uni.setStorageSync(key, JSON.stringify(value))
}
function getStorage(key) {
const value = uni.getStorageSync(key)
try {
return JSON.parse(value)
} catch {
return value
}
}6. 图片与媒体兼容问题
6.1 图片加载问题
问题:
- 小程序图片需要配置合法域名
- 本地图片路径格式不同
- 图片缓存机制不同
解决方案:
// 使用网络图片
<image src="https://cdn.example.com/image.png" />
// 本地图片统一路径
<image src="/static/logo.png" />
// 图片懒加载
<image lazy-load :src="imageUrl" />
// 图片加载失败处理
<image
:src="imageUrl"
@error="onImageError"
/>6.2 视频播放兼容
问题:
- 视频自动播放限制
- 视频层级问题
- 视频格式支持差异
解决方案:
// 自动播放需要用户交互
<video
:src="videoSrc"
:autoplay="false"
@click="playVideo"
/>
// 层级问题使用 cover-view
<video :src="videoSrc">
<cover-view>覆盖内容</cover-view>
</video>
// 格式兼容
// 优先使用 mp4 格式
// H5 端支持更多格式6.3 音频播放兼容
问题:
- 背景音乐播放限制
- 音频自动播放限制
解决方案:
// 使用 uni.createInnerAudioContext
const audio = uni.createInnerAudioContext()
audio.src = 'https://cdn.example.com/audio.mp3'
// 用户交互后播放
<button @click="playAudio">播放</button>
function playAudio() {
audio.play()
}7. 权限与安全兼容问题
7.1 定位权限
问题:
- 不同平台定位 API 差异
- 权限申请流程不同
- H5 需要 HTTPS
解决方案:
// 统一定位封装
function getLocation() {
return new Promise((resolve, reject) => {
uni.getLocation({
type: 'gcj02',
success: resolve,
fail: (err) => {
// 权限被拒绝,引导用户开启
// #ifdef APP-PLUS
plus.runtime.openURL('app-settings:')
// #endif
// #ifdef MP-WEIXIN
uni.showModal({
title: '提示',
content: '请在设置中开启定位权限',
success: (res) => {
if (res.confirm) {
uni.openSetting()
}
}
})
// #endif
}
})
})
}7.2 相机相册权限
问题:
- 权限申请时机不同
- 权限拒绝处理
解决方案:
// 选择图片
function chooseImage() {
uni.chooseImage({
count: 9,
success: (res) => {
console.log(res.tempFilePaths)
},
fail: (err) => {
// 权限被拒绝
uni.showModal({
title: '提示',
content: '请在设置中开启相册权限',
success: (res) => {
if (res.confirm) {
// #ifdef MP-WEIXIN
uni.openSetting()
// #endif
}
}
})
}
})
}7.3 网络权限
问题:
- 小程序域名白名单
- H5 跨域限制
解决方案:
// 小程序:配置合法域名
// manifest.json -> mp-weixin -> urlCheck: false(开发环境)
// H5:配置代理
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true
}
}
}
}8. 性能兼容问题
8.1 setData 性能
问题:
- 小程序 setData 频繁调用导致卡顿
- 大数据量传输慢
解决方案:
// 合并数据更新
// 错误示例
this.list.forEach((item, index) => {
this.list[index].checked = true
})
// 正确示例
const newList = this.list.map(item => ({
...item,
checked: true
}))
this.list = newList
// 局部更新
this.$set(this.list, index, newItem)8.2 长列表性能
问题:
- 大量数据渲染卡顿
- 滚动性能差
解决方案:
// 使用虚拟列表
<scroll-view
scroll-y
:style="{ height: '100vh' }"
@scroll="onScroll"
>
<view
v-for="item in visibleList"
:key="item.id"
>
{{ item.name }}
</view>
</scroll-view>
// 计算可见区域数据
computed: {
visibleList() {
const start = Math.floor(this.scrollTop / this.itemHeight)
const end = start + this.visibleCount
return this.list.slice(start, end)
}
}
// 或使用第三方虚拟列表组件
// 如:z-paging、virtual-list8.3 图片性能
问题:
- 大图片加载慢
- 内存占用高
解决方案:
// 图片压缩
<image
:src="imageUrl + '?x-oss-process=image/resize,w_750'"
mode="aspectFill"
/>
// 图片懒加载
<image lazy-load :src="imageUrl" />
// 使用 webp 格式
<image :src="imageUrl + '?format=webp'" />
// 预加载关键图片
function preloadImages(urls) {
urls.forEach(url => {
uni.getImageInfo({ src: url })
})
}9. 平台特性兼容问题
9.1 微信小程序特有问题
问题:
- 获取用户信息需要 button 触发
- 订阅消息需要用户授权
- 分享需要 onShareAppMessage
解决方案:
// 用户信息
<button open-type="getUserInfo" @getuserinfo="getUserInfo">
获取用户信息
</button>
// 订阅消息
wx.requestSubscribeMessage({
tmplIds: ['模板ID'],
success: (res) => {
console.log(res)
}
})
// 分享
onShareAppMessage() {
return {
title: '分享标题',
path: '/pages/index/index',
imageUrl: '/static/share.png'
}
}9.2 支付宝小程序特有问题
问题:
- API 命名差异(my vs wx)
- 授权流程不同
- 支付接口不同
解决方案:
// 条件编译
// #ifdef MP-ALIPAY
my.getAuthCode({
scopes: 'auth_user',
success: (res) => {
console.log(res.authCode)
}
})
// #endif9.3 App 端特有问题
问题:
- 状态栏高度适配
- 底部安全区适配
- 原生插件兼容
解决方案:
// 状态栏高度
const systemInfo = uni.getSystemInfoSync()
this.statusBarHeight = systemInfo.statusBarHeight
// 安全区适配
<view :style="{ paddingBottom: safeAreaBottom + 'px' }">
内容
</view>
computed: {
safeAreaBottom() {
const info = uni.getSystemInfoSync()
return info.safeAreaInsets?.bottom || 0
}
}
// 原生插件
// #ifdef APP-PLUS
const plugin = uni.requireNativePlugin('PluginName')
plugin.method()
// #endif9.4 H5 端特有问题
问题:
- 路由模式差异
- history 模式需要服务器配置
- 微信 SDK 兼容
解决方案:
// 路由配置
// manifest.json -> h5 -> router -> mode: 'hash'
// history 模式服务器配置(Nginx)
location / {
try_files $uri $uri/ /index.html;
}
// 微信 SDK
// #ifdef H5
import wx from 'weixin-js-sdk'
wx.config({
appId: '',
timestamp: '',
nonceStr: '',
signature: '',
jsApiList: []
})
// #endif10. 调试与开发兼容问题
10.1 开发环境差异
问题:
- H5 热更新快,小程序慢
- 真机调试流程不同
- 控制台日志差异
解决方案:
// 统一日志封装
function log(...args) {
// #ifdef H5
console.log('%c[LOG]', 'color: blue', ...args)
// #endif
// #ifdef MP-WEIXIN || APP-PLUS
console.log('[LOG]', ...args)
// #endif
}
// 真机调试
// 小程序:开发者工具 -> 预览/真机调试
// App:HBuilderX -> 运行到手机或模拟器
// H5:浏览器开发者工具10.2 条件编译调试
问题:
- 条件编译代码无法跨平台调试
- 需要多次编译测试
解决方案:
// 开发时使用环境变量
const isDev = process.env.NODE_ENV === 'development'
if (isDev) {
// 开发环境代码
}
// 条件编译
// #ifdef MP-WEIXIN
// 微信小程序代码
// #endif10.3 错误捕获
问题:
- 不同平台错误信息格式不同
- 需要统一错误处理
解决方案:
// 全局错误捕获
// App.vue
onError(err) {
console.error('全局错误:', err)
// 上报错误
// #ifdef APP-PLUS
plus.runtime.arguments
// #endif
// #ifdef MP-WEIXIN
wx.reportAnalytics('error', {
error: err
})
// #endif
}
// Promise 错误
onUnhandledRejection(err) {
console.error('Promise 错误:', err)
}