uni-app 转微信小程序 · 避坑与实战全记录
uni-app 一端多套 最适合「轻交互、重展示、预算紧、周期短」的项目,不属于此范畴的自动避让,别掉进去了
这是一份从真实项目血泪史中整理出的 uni-app → 微信小程序 迁移指南。
覆盖分包、样式、组件、API、交互、性能等 全链路 问题,附完整代码片段与官方文档溯源。
建议收藏,随查随用。
📌 小程序包体积限制
根据微信小程序的限制:
● 主包大小:不超过 2MB。
● 单个分包大小:不超过 2MB。
● 所有分包总大小:不超过 20MB。
因此,合理地管理和划分模块的引用关系,对于控制包体积至关重要。
一、分包机制
分包之后,打包完成,分包与分包之间就不能互相调用内容。
分包可以调用主包的内容
1.1 官方限制速查表
维度 | 主包 | 普通分包 | 独立分包(independent) |
---|---|---|---|
大小上限 | 2 MB | 2 MB | 无上限 |
启动时下载 | 必下载 | 用时下载 | 只下载自己 |
能否 import 主包 | ✅ | ✅ | ❌ |
能否 import 其他分包 | ❌ | ❌ | ❌ |
能否被组件引用 | ✅ | ✅ | ❌ |
适用场景 | 启动页/TabBar | 业务模块 | 登录页/广告页 |
1.2 目录结构最佳实践
arduino
├─ common/ // 主包公共资源(必须被主包页面引用)
│ ├─ utils/
│ ├─ components/
│ └─ static/
├─ pages/ // 主包页面(越少越好)
├─ subPackages/
│ ├─ moduleA/ // 普通分包
│ │ ├─ pages/
│ │ └─ components/ // 只能当前分包用
│ └─ moduleB/ // 独立分包
│ └─ pages/
└─ pages.json
示例说明
假设你的项目结构如下:
markdown
markdown
复制编辑
- utils/
- common.js
- helper.js
- pages/
- index.vue // 主包页面
- subPackages/
- feature/
- page.vue // 分包页面
● 如果 pages/index.vue 引用了 utils/common.js,则 common.js 会被打包进主包。
● 如果 subPackages/feature/page.vue 引用了 utils/helper.js,而主包中没有引用 helper.js,则 helper.js 不会被打包进主包。
● 如果你希望 helper.js 被主包和分包共同使用,确保在主包中也引用了 helper.js。
1.3 分包配置示例(pages.json)
json
{
"subPackages": [
{
"root": "subPackages/moduleA",
"pages": [...]
},
{
"root": "subPackages/moduleB",
"independent": true,
"pages": [...]
}
]
}
分包的 independent
● 加了 "independent": true:这个分包是独立分包 → 启动时不加载主包代码,可突破主包 2 MB,但不能复用主包/其他分包的任何代码(包括 util、store、组件、npm 包)。
● 不加(默认):普通分包 → 启动时先下载主包,再下载该分包,可以复用主包代码,但主包总体积仍受 2 MB 限制。
详细对比
维度 | 独立分包 independent=true | 普通分包(默认) |
---|---|---|
启动下载 | 只下载自己,不下载主包 | 先下载主包,再下载自己 |
主包大小限制 | 主包可<2 MB,独立包无上限 | 主包+所有普通包 ≤ 2 MB |
代码复用 | ❌ 不能复用主包/其他分包 | ✅ 可以复用主包代码 |
包间引用 | ❌ 不能 import 主包/其他分包 | ✅ 可以 import 主包 |
适用场景 | 启动页/登录页/广告页等独立功能 | 普通业务模块,需要复用公共代码 |
使用建议
● 主包已接近 2 MB → 选 独立分包,把重依赖(图表、播放器)放进去。
● 需要复用公共工具/组件 → 选 普通分包,否则你得把公共代码再拷一份到独立包里,体积翻倍。
总结
"独立分包"就像独立 App,启动快但自给自足;普通分包像插件,启动慢但可共享主包代码。
1.4 跨包引用失败的 4 种典型错误
错误信息 | 原因 | 修复 |
---|---|---|
module not found | 主包引用分包文件 | 把文件移到 common/ |
can't require subPackages/... | 分包互相引用 | 抽离公共文件到主包 |
超过 2 MB | 主包体积爆炸 | 把大模块拆成独立分包 |
独立分包无法使用 uView | 独立分包不能复用主包 npm | 把 uView 再装一份到独立分包(牺牲体积) |
1.5 强制把文件打进主包的黑科技
在 vue.config.js 或 vite.config.ts 里:
css
// vite-plugin-uni 示例
plugins: [
uni(),
{
name: 'force-include',
config() {
return {
optimizeDeps: {
include: [
'@/common/utils/date.ts', // 即使没引用也打进主包
]
}
}
}
}
]
二、样式穿透 · 解决方案
在将 UniApp 项目转换为微信小程序时,使用 ::v-deep 进行样式穿透可能会遇到不生效的问题。这是由于微信小程序的组件样式隔离机制所致。以下是解决该问题的建议:
2.1 微信小程序样式隔离机制
● 默认 styleIsolation: 'isolated'(组件样式互不影响)
● 修改第三方组件样式 → 必须改成 shared
2.2 不同框架写法对照
框架 | 解除隔离写法 | 穿透语法 |
---|---|---|
Vue2 | export default { options:{styleIsolation:'shared'} } | ::v-deep .a{} |
Vue3 | defineOptions({options:{styleIsolation:'shared'}}) | :deep(.a){} |
全局样式 | 在 App.vue 写样式 | 无需穿透 |
2.3 完整示例(Vue3 + uView)
xml
<template>
<u-upload class="my-upload" />
</template>
<script setup>
defineOptions({
options: { styleIsolation: 'shared' }
})
</script>
<style scoped>
.my-upload :deep(.u-upload__button) {
border-radius: 16rpx !important;
}
</style>
2.4 预处理器差异速查
推荐使用: ::v-deep
预处理器 | 支持语法 |
---|---|
css | >>>, /deep/, ::v-deep |
less | /deep/, ::v-deep |
dart-sass | 仅 ::v-deep 或 :deep() |
node-sass | 可以使用 ::v-deep |
⚠️ 注意事项
● 样式隔离设置的位置:options 配置应放在组件的 export default 对象中,而不是 script setup 语法中。
● 样式作用范围:解除样式隔离后,父组件的样式可能会影响子组件,需注意可能引起的样式冲突。
● 平台差异:某些样式穿透方法在不同平台(如 H5、App、小程序)中的支持情况可能不同,需根据目标平台进行测试和调整
三、组件通信 · 100% 成功 checklist
场景 | H5 写法 | 小程序兼容写法 |
---|---|---|
传递对象 prop | :data-source="list" | ✅ 正常 |
传递函数 prop | @on-change="fn" | ✅ 正常 |
事件名大小写 | @onChange | ❌ 必须全小写 @on-change |
动态插槽 | ✅ 正常 | |
默认插槽为空 | slots.default?.() | ✅ 防御式调用 |
3.1 emit 命名必知
kotlin
// 子组件
this.$emit('select-change', value) // 小程序中模板必须写 kebab-case
xml
<!-- 父组件 -->
<child @select-change="handle" />
示例1:
✅ 严格按照 相同的写法
kotlin
// 子组件发送
this.$emit('on-select-changed', arr)
// 父组件监听
@on-select-changed="handleSelectedDoctors"
❌不相同写法,h5可用,小程序传递失败
php
// 父组件传递参数
<doctor-list :data-source="mainExpertList"></doctor-list>
// 子组件,接收不到dataSource内容
const props = defineProps({
dataSource: {
type: Array,
default: () => []
}
})
四、 交互异常 · 真机调试玄学
4.1 textarea 高度错乱(官方 bug)
出现问题,页面内部处理不展示了,但是内容的字体定位会错乱。悬浮再页面上方
解决办法:【uni-app】 textarea组件的auto-hieght属性,显隐切换时高度异常,无法自适应内容撑开。_uniapp textarea auto-height-CSDN博客
同理,要组件展示出现,才赋值 this.showAutoHeight = true
ini
<textarea class="text-area border-b"
v-model="applyInfo.consult_case_history_other" :auto-height="showAutoHeight"
placeholder="请填写患者治疗经过(500字以内)"/>
● 现象:v-if 切换后 auto-height 失效
● 根因:微信小程序必须先渲染再赋值
● 修复:
kotlin
// 错误
this.show = true
this.text = '很长很长...'
// 正确
this.show = true
this.$nextTick(() => {
this.text = '很长很长...'
})
4.2 input 点击两次才触发清除
使用fouce input框时候,会弹出输入的按键,点击第一次,是优先对input框失焦阻塞了自身事件,
点击第二次,才是真正处理事件
● 现象:键盘弹起后第一次点击无效
● 原理:第一次点击先失焦,第二次才真正触发
● 修复:优先考虑使用延迟处理
csharp
async clearInput() {
this.isFocus = false // 关闭键盘
await this.$nextTick() // 等键盘收起
this.value = '' // 再清空
}
4.3 v-show 与 css样式 display:flex 冲突
● 现象:v-show="false" 仍占位
● 原因:display:flex 优先级高于 display:none
● 修复:
css
/* 用 v-if 或加一层 wrapper */
.wrapper.hidden {
display: none !important;
}
五、API 差异 · 平台判断大全
功能 | H5 实现 | 小程序实现 |
---|---|---|
Blob 转 URL | URL.createObjectURL(blob) | uni.arrayBufferToBase64 |
下载文件 | a.href = url | uni.downloadFile |
页面栈 | 无限制 | 最多 10 层 |
返回上一页 | history.back() | uni.navigateBack() |
刷新当前页 | location.reload() | const pages = getCurrentPages(); pages[pages.length-1].onLoad() |
5.1 Blob 转 URL
请求图片展示
javascript
// h5处理
const blob = new Blob([res.data], {
type: res.headers['content-type']
})
return URL.createObjectURL(blob)
// 小程序处理
let base64 = uni.arrayBufferToBase64(res.data)
return `data:${res.headers['content-type']};base64,${base64}`
5.2 页面栈清理最佳实践
10层的栈,如果超出就会不容许再次跳转

php
// 关键业务完成后主动清理
uni.redirectTo({ url: '/pages/index/index' }) // 替换当前页
切记要及时的清理栈
六、性能优化 · 体积与启动
6.1 主包瘦身 5 步法
-
图片放 CDN:用网络地址替代本地
-
npm 包按需引入:如 lodash → lodash-es
-
动态 import:路由级/组件级懒加载
-
独立分包:把重依赖(图表、富文本)拆出去
-
构建分析:
arduino
npm run build:mp-weixin --analyze
6.2 独立分包实战(uView 图表)
json
// subPackages/chart/package.json
{
"dependencies": {
"uview-plus": "3.x" // 独立再装一份
}
}
七、安全与适配
场景 | 代码示例 |
---|---|
底部安全距离 | padding-bottom: env(safe-area-inset-bottom) |
自定义导航返回 | 「自定义返回监听」:components\interceptor |
横屏适配 | "pageOrientation": "auto" in pages.json |
7.1 安全距离
css 安全距离
css
.btn-content{
padding-bottom: env(safe-area-inset-bottom);
padding-bottom: constant(safe-area-inset-bottom); /* iOS <= 11.2 */
}
八、 配置
8.1不在以下 request 合法域名列表中
开始初期可以没有配置系列地址,需要去设置勾选 微信 --》右侧详情 --》本地设置

如遇到未收录的问题。 祝开发顺利,少踩坑,多提效!