"一次开发,多端运行"是 uni-app 的招牌,但真实项目里各平台差异无处不在:微信有开放能力、H5 有跨域、App 有原生权限。条件编译(#ifdef)就是 uni-app 处理这些差异的官方答案。本文讲透条件编译的语法、边界和多端适配的完整套路。
一、条件编译基础
语法:#ifdef / #ifndef / #endif
// #ifdef MP-WEIXIN
console.log('只有微信小程序端会执行')
// #endif
// #ifndef H5
console.log('除了 H5 端,其他端都执行')
// #endif
// #ifdef MP-WEIXIN || MP-ALIPAY
console.log('微信或支付宝小程序')
// #endif有效平台标识:
| 标识 | 平台 |
|---|---|
| H5 | Web(含浏览器/移动端浏览器) |
| MP-WEIXIN | 微信小程序 |
| MP-ALIPAY | 支付宝小程序 |
| MP-BAIDU | 百度小程序 |
| MP-TOUTIAO | 抖音小程序 |
| APP-PLUS | App(5+ App / nvue) |
| APP-PLUS-NVUE | 仅 nvue 页面 |
条件编译可以用在哪里
不只是 JS——template、css、json 配置都能用:
<template>
<view>
<!-- #ifdef MP-WEIXIN -->
<button open-type="share">分享给好友</button>
<!-- #endif -->
<!-- #ifdef H5 -->
<button @click="copyLink">复制链接</button>
<!-- #endif -->
</view>
</template>
<style>
/* #ifdef H5 */
.page { cursor: pointer; }
/* #endif */
</style>注意 template 中的写法是 HTML 注释形式 <!-- #ifdef -->,CSS 中是块注释 /* #ifdef */,JS 中是行注释 // #ifdef。三种注释形态对应三种文件区域,写错注释类型编译器识别不到。
pages.json 里的条件编译
连页面注册都能按平台区分:
{
"pages": [
{ "path": "pages/index/index" },
// #ifdef MP-WEIXIN
{ "path": "pages/live/live" },
// #endif
// #ifdef APP-PLUS
{ "path": "pages/push/push" },
// #endif
{ "path": "pages/mine/mine" }
]
}微信端才有直播间页,App 端才有推送设置页——页面级的差异化直接在源头解决。
二、多端 API 差异处理
支付:三端三套流程
支付是平台差异最深的领域,三端只能分别实现:
// utils/pay.js
export function pay(orderId) {
return new Promise((resolve, reject) => {
// #ifdef MP-WEIXIN
uni.requestPayment({
provider: 'wxpay',
// 后端统一下单返回的微信支付参数
timeStamp: payParams.timeStamp,
nonceStr: payParams.nonceStr,
package: payParams.package,
signType: payParams.signType,
paySign: payParams.paySign,
success: resolve,
fail: reject
})
// #endif
// #ifdef APP-PLUS
uni.requestPayment({
provider: 'alipay', // App 端可自主选择支付宝/微信
orderInfo: payParams.orderInfo,
success: resolve,
fail: reject
})
// #endif
// #ifdef H5
// H5 微信支付需要跳转 JSAPI 或唤起微信客户端
location.href = payParams.mwebUrl
// #endif
})
}登录:获取用户信息的方式差异
export function login() {
// #ifdef MP-WEIXIN
// 小程序:wx.login 换 code
const [err, res] = await uni.login({ provider: 'weixin' })
return await request.post('/auth/wx-login', { code: res.code })
// #endif
// #ifdef APP-PLUS
// App:可以接手机号一键登录
const [loginErr, loginRes] = await uni.login({ provider: 'univerify' })
return await request.post('/auth/phone-login', {
access_token: loginRes.authResult.access_token,
openid: loginRes.authResult.openid
})
// #endif
// #ifdef H5
// H5:跳转微信 OAuth 网页授权
const redirectUri = encodeURIComponent(location.origin + '/auth-callback')
location.href = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_userinfo`
// #endif
}API 可用性检测:uni.canIUse
平台差异不明确时,运行时检测比条件编译更灵活:
if (uni.canIUse('getUserProfile')) {
uni.getUserProfile({ desc: '用于完善会员资料' })
} else {
// 基础库不支持时的降级方案
uni.showModal({ title: '提示', content: '请升级微信版本' })
}三、样式多端适配
rpx 的本质与陷阱
rpx 以 750 屏宽为基准:750rpx = 屏幕宽度。设计稿 375px 宽的图,标注多少 px 就写多少 × 2 的 rpx,换算自动化。
但 rpx 有几个陷阱:
- H5 端 rpx 基于视口宽,横屏或宽屏设备(iPad)上元素会被拉得离谱
/* #ifdef H5 */
/* H5 端用 max-width 约束内容区 */
.page { max-width: 500px; margin: 0 auto; }
/* #endif */- 1rpx 边框在部分设备上不显示:物理像素取整导致,用
1px+ transform 或背景图实现细边框更稳 - 字体大小慎用 rpx:大屏上字会跟着放大,正文字号建议用 px
安全区适配(刘海屏)
.safe-bottom {
/* 苹果底部安全区 */
padding-bottom: constant(safe-area-inset-bottom); /* iOS < 11.2 */
padding-bottom: env(safe-area-inset-bottom); /* iOS >= 11.2 */
}// pages.json 页面配置
{
"path": "pages/index/index",
"style": {
"navigationStyle": "custom", // 自定义导航栏需要自己处理状态栏高度
"navigationBarTitleText": "首页"
}
}自定义导航栏时获取状态栏高度:
onLoad() {
const sysInfo = uni.getSystemInfoSync()
this.statusBarHeight = sysInfo.statusBarHeight
}四、多端架构组织实践
差异代码分层而不是满屏 #ifdef
条件编译用多了,代码可读性会急剧恶化。推荐架构:接口统一,实现分平台目录:
src/
├── platform/
│ ├── wx/
│ │ ├── pay.js # 微信小程序支付实现
│ │ └── login.js
│ ├── app/
│ │ ├── pay.js # App 支付实现
│ │ └── login.js
│ └── h5/
│ ├── pay.js # H5 支付实现
│ └── login.js
└── utils/
└── pay.js # 统一入口// utils/pay.js —— 收敛所有条件编译到一个文件
// #ifdef MP-WEIXIN
import pay from '@/platform/wx/pay'
// #endif
// #ifdef APP-PLUS
import pay from '@/platform/app/pay'
// #endif
// #ifdef H5
import pay from '@/platform/h5/pay'
// #endif
export default pay业务代码只 import 统一入口,完全无感知平台差异。条件编译被压缩在入口文件里,各端实现独立演进互不干扰。
何时用条件编译,何时用运行时判断
| 场景 | 方案 |
|---|---|
| 代码只对某端存在(API、页面、组件) | 条件编译 |
| 某端逻辑完全不同 | 条件编译 + 分平台目录 |
| 只是参数/样式的小差异 | 运行时判断 + CSS 条件编译 |
原则:能在编译期裁剪掉的代码不要留到运行时,包体积是小程序的硬指标。
五、调试技巧
- H5 端开发效率最高,先在 H5 调通逻辑再上小程序真机
process.env.UNI_PLATFORM可在 JS 中判断当前编译平台(vite 版本),用于日志过滤- 小程序端用微信开发者工具的"编译模式"自定义启动页面和参数
- 差异 bug 高发区:时间日期格式(iOS 不认
2026-08-23短横线格式,要替换成/)、position: fixed在部分安卓机的表现、webview 与原生通信
// iOS 日期兼容:经典坑
const date = new Date('2026-08-23 12:00') // iOS 返回 Invalid Date
const fixed = new Date('2026-08-23 12:00'.replace(/-/g, '/')) // OK总结
- 条件编译三种注释形态分别对应 JS / template / CSS 区域
- pages.json 支持条件编译,页面级差异从注册源头解决
- "接口统一、实现分层"的架构能把 #ifdef 压缩到入口文件
- 能编译期裁剪就不运行时判断,控制包体积
- 高频坑:rpx 横屏、1rpx 边框、iOS 日期格式、uploadFile 字符串响应
多端开发的真实成本不在"写代码",而在"记住差异"。把本文的分层架构和坑清单用起来,多端项目就能保持可控。
评论 (0)