一个用uniapp写的项目,同时要出微信小程序和H5版本。最开始觉得uniapp都能编译过去,应该没啥问题。结果遇到支付功能,小程序要用wx.requestPayment,H5得引入第三方支付SDK,两者参数完全不同,甚至回调机制都是两套。我一开始在业务代码里写各种运行时判断:if (platform === ‘weixin’) … else …。后来越加越多,连调接口的URL都不一样,代码乱到没法维护。
后来单位老前辈提了一句:“干嘛不用条件编译?” 我这才去翻了文档。用完之后想说:真香。今天就把我那次的改造过程写出来。
条件编译到底是什么?
简单讲,条件编译是“在编译阶段”根据当前打包的平台,把不需要的代码直接从源文件中剔除。它和普通的if判断完全是两码事:if是运行时判断,所有平台的代码都保留在包里;条件编译后,你写在小程序里的那段代码,在打H5包时根本不存在了。这样既能避免执行错误,也能减小包体积。
uniapp里的写法无非两种:#ifdef 表示“如果定义了某平台就保留”,#ifndef 表示“如果没定义某平台就保留”。最后用#endif结尾。要注意的是,在模板、脚本、样式的注释写法各有不同,别写混了。
基本语法一览
在script中,这样写:
// #ifdef H5
console.log('这是H5平台')
// #endif
// #ifndef MP-WEIXIN
console.log('不是微信小程序平台')
// #endif
在template里,要写在html注释中:
<!-- #ifdef H5 -->
<view>H5才显示的视图</view>
<!-- #endif -->
在style中,用css注释:
/* #ifdef MP-WEIXIN */
.button { width: 200px; }
/* #endif */
很多人第一次用容易忘记结束符,或者把#ifdef写成了#if defined,这些都要注意。
我做的支付案例
当时我的目录结构是这样:
src/
├── api/
│ └── pay.js
├── pages/
│ └── order/
│ └── pay.vue
最初我把所有平台的支付逻辑都堆在pay.vue里面,看起来就是这个样子:
// #ifdef MP-WEIXIN
uni.requestPayment({
provider: 'wxpay',
orderInfo: res.data.payParams,
success: () => {
uni.showToast({ title: '支付成功' });
}
});
// #endif
// #ifdef H5
// 假设H5用的支付宝的SDK
window.AlipayJSBridge.call('tradePay', {
orderStr: res.data.orderStr
}, (result) => {
if (result.resultCode === '9000') {
uni.showToast({ title: '支付成功' });
}
});
// #endif
虽然能跑,但这段代码放在一个页面里实在难看,尤其是支付成功后还要跳转不同页面、处理不同参数,混在一起我自己都不想看。后来我抽了三个文件:
src/
├── api/
│ ├── pay.js
│ ├── pay.mp.js
│ └── pay.h5.js
然后在pay.js里做基础封装,再通过条件编译引用不同平台的实现:
import base from './pay.js';
// #ifdef MP-WEIXIN
import mpPay from './pay.mp.js';
// #endif
// #ifdef H5
import h5Pay from './pay.h5.js';
// #endif
export default {
pay(params) {
// #ifdef MP-WEIXIN
return mpPay.pay(params);
// #endif
// #ifdef H5
return h5Pay.pay(params);
// #endif
}
}
这样在页面里只需要调用统一的pay(params),剩下的事各自平台自己处理。改动就很小,而且以后要加App端,再加一个pay.app.js,然后加一行条件编译就行,完全不影响其他地方。
模板里的条件编译:处理不同平台的UI差异
除了逻辑,有时候界面也需要不同。比如支付页底部有个“使用优惠券”按钮,微信小程序里要用button open-type="contact",H5上却是个普通按钮,点击唤起客服聊天。遇到这种情况,模板条件编译也能直接隔离。
<!-- #ifdef MP-WEIXIN -->
<button open-type="contact">联系客服</button>
<!-- #endif -->
<!-- #ifdef H5 -->
<button @click="contactH5">联系客服</button>
<!-- #endif -->
没有条件编译之前,我得在数据里定义isWeixin,然后v-if判断,代码又长又丑。现在这种注释式写法,打包时直接留下对应平台那块,清爽多了。
踩过的几个坑
1. 条件编译是编译期,不是运行期
一开始我犯了个错,我在条件编译里写了一个变量,然后在外部试图访问它,结果报错找不到变量。原因是那段代码在另一个平台被剔除了,根本不存在。所以如果你需要定义公共变量,把它放在条件编译外面。
2. 嵌套条件编译很容易乱
比如你想同时兼容小程序和H5,但又要排除App,写成这样:
// #ifdef MP-WEIXIN || H5
console.log('小程序或H5');
// #endif
这个语法其实是支持的,但要注意不要和#ifndef混用产生逻辑冲突。我建议能拆分就拆分,别一个条件里写太多。
3. template里的条件编译注释不能加空格
<!-- #ifdef H5 --> 这种写法是允许的,但如果你在 #ifdef 前多了空格,某些版本可能识别不了。最好严格按照官方示例来,别自己加`#ifdef H5`里面带空格。我试过<!-- #ifdef H5 -->是合法的,但注释结束符-->前面有没有空格都行。
4. 样式条件编译最好配合使用
有时候某个平台需要特殊间距,我会在style里写:
/* #ifdef MP-WEIXIN */
.pay-button { margin-top: 20rpx; }
/* #endif */
但是注意:这不会自动加上选择器权重,如果你的基础样式写在普通选择器里,条件编译的样式没有「更晚加载」的优势,可能会被覆盖。解决办法是把条件编译的样式写在后面,或者提升权重。
5. 微信小程序的特殊变量不能乱用
在小程序里可以用wx全局变量,在H5里没有。如果你在条件编译里用了wx.getStorageSync,并且打包到H5,这段代码会被剔除,没问题。但如果你不小心在外面引入了wx的API,照样报错。条件编译不是万能护身符,业务代码里还是得注意平台差异。
什么时候不该用条件编译
如果只是某个平台的小差异,比如字体大小、颜色值,直接用CSS变量或者平台类名就行,别搞太多条件编译,代码反而更难阅读。条件编译最好用于那些“完全不同的实现”或者“不同平台的API调用”上。
另外,如果你的项目将来想发布到小程序、H5、App,条件编译是必须的。但建议在每个使用条件编译的地方加一行注释,说明为什么需要,比如“H5支付不出App,这里单独处理”,不然同事看到这段代码容易骂人。
总结
自从用了条件编译,我那个支付页面的代码量少了一半,而且排查问题时一眼就能看到当前平台真正执行的是哪一段。uni-app本身已经做了很多跨端兼容,但总有一些平台不可调和的差异,这时候条件编译就是最后的底牌。
如果你也遇到“H5好好的,小程序死活不对”或者“小程序能行,APP又不行”的情况,不妨检查一下是不是该做平台隔离。别再用if了,试试条件编译,真的一身轻。

