mpvue开发实战中的坑与高效解决方案
为什么我又回头折腾 mpvue?
去年项目收尾后,我一度以为自己再也不会碰 mpvue 了。毕竟现在 uni-app 都快成行业默认选项了,社区活跃、文档齐全、H5/小程序/APP 全端通吃。但最近接手一个老项目维护,发现它还是基于 mpvue 写的——而且因为历史原因没法直接迁移到 uni-app。于是我又被迫重新拾起这套“古董”技术栈。
过程中自然要选型:是硬着头皮用原生 mpvue 写下去,还是引入一些第三方方案优化体验?对比下来,其实主要就三种路子:纯 mpvue 原生写法、mpvue + 自定义 mixin 封装、或者强行套一层类似 Vue 3 的 Composition API(虽然官方不支持)。今天就聊聊这三种方案的实际体验,不讲理论,只说我踩过的坑和偷过的懒。
谁更灵活?谁更省事?
先说结论:如果你只是修 bug 或加几个小页面,原生 mpvue 最省事;但如果你要重构模块、写复杂交互,我强烈建议上自定义 mixin,哪怕多写几行代码,后期维护能少掉一半头发。
原生 mpvue 的写法大家都熟,就是标准的 Vue 2 选项式 API,生命周期用 onShow、onLoad 这些小程序钩子:
export default {
data() {
return {
list: []
}
},
onLoad() {
this.fetchData()
},
methods: {
async fetchData() {
const res = await fetch('https://jztheme.com/api/list')
this.list = await res.json()
}
}
}
看起来没问题,对吧?但问题出在复用性上。比如你有五个页面都要调用类似的接口、处理 loading、错误提示,你就得复制粘贴五遍。我之前就这么干过,结果改个 loading 样式,全项目 grep 替换,还漏了一个页面,上线后被 QA 打回来三次。
后来我搞了个通用的 useApi mixin,把请求逻辑抽出来:
// mixins/useApi.js
export default {
data() {
return {
loading: false,
error: null
}
},
methods: {
async request(url, options = {}) {
this.loading = true
this.error = null
try {
const res = await fetch(url, options)
if (!res.ok) throw new Error('Request failed')
return await res.json()
} catch (err) {
this.error = err.message
console.error('API Error:', err)
} finally {
this.loading = false
}
}
}
}
然后在页面里直接混入:
import useApi from '@/mixins/useApi'
export default {
mixins: [useApi],
async onLoad() {
this.list = await this.request('https://jztheme.com/api/list')
}
}
这一套下来,不仅代码量少了,逻辑也集中了。改个错误处理策略,只改 mixin 一处就行。我比较喜欢这种“一次封装,到处偷懒”的方式。
Composition API?别折腾了
有人可能会问:能不能用 Vue 3 的 setup 语法?比如用 @vue/composition-api 这个包?
理论上可以,但在 mpvue 里坑特别多。我试过,基本不可行。原因很简单:mpvue 的底层编译机制是基于 Vue 2 的,它把 template 编译成小程序的 wxml,而 Composition API 的响应式系统和模板编译流程跟这个不兼容。你写个 ref,页面可能根本不会更新;或者 onMounted 触发时机不对,数据还没回来就渲染了。
折腾了两天,最后发现还不如老老实实用 mixin。除非你愿意 fork mpvue 源码自己改编译器——那成本太高了,不值得。
性能对比:差距比我想象的大
很多人以为 mpvue 性能差是因为“转译层”,其实日常开发中,**性能瓶颈更多出在写法上**。比如我见过有人在 v-for 里直接放复杂计算,或者在 onShow 里频繁 setData。
举个例子,原生写法如果没注意,很容易写出这种代码:
// 千万别这么干!
onShow() {
this.list = this.rawList.map(item => ({
...item,
displayName: item.name.toUpperCase().replace(/s+/g, '_')
}))
}
每次页面显示都重新计算整个列表,如果 list 有上百条,小程序会明显卡顿。而用 mixin 封装后,我可以加个缓存机制:
methods: {
getProcessedList() {
if (this._cachedList) return this._cachedList
this._cachedList = this.rawList.map(...)
return this._cachedList
}
}
或者更狠一点,用 computed(mpvue 支持):
computed: {
processedList() {
return this.rawList.map(item => ({ ... }))
}
}
这样只有 rawList 变了才重新计算。亲测有效,页面滑动流畅度提升明显。
所以性能差距不在框架本身,而在你怎么用。用得好,mpvue 也能跑得飞快;用得糙,uni-app 一样卡成 PPT。
我的选型逻辑
现在如果让我重新选,我会这么判断:
- 如果是新项目,直接上 uni-app,别犹豫。生态、工具链、跨端能力都碾压 mpvue。
- 如果是老 mpvue 项目维护,且改动不大,就用原生写法,避免引入新复杂度。
- 如果是老项目要加新功能、重构模块,我一定上 mixin 封装。哪怕只封装 loading 和 request,也值得。
这里注意我踩过好几次坑:不要试图在 mpvue 里搞 Vuex 复杂状态管理。小程序本身页面隔离强,全局状态用多了反而容易出 bug,比如页面返回时状态残留。我现在的做法是,能用 props 传就传,实在不行再用简单的 globalData(App.vue 里挂个对象),比 Vuex 轻量太多。
另外,mpvue 的 slot 插槽支持很弱,尤其是作用域插槽,在部分小程序平台(比如百度)会失效。我后来干脆放弃动态插槽,改用 render 函数或者直接传组件名+props,虽然啰嗦点,但稳定。
结尾:别追求完美,够用就行
mpvue 确实老了,但老技术不代表不能用。关键是你怎么用它。我现在的原则是:**不追求技术先进,只求交付稳定、维护省心**。能用 mixin 解决的,绝不写重复代码;能用 computed 优化的,绝不放 methods 里硬算。
以上是我个人对 mpvue 不同技术方案的实战对比总结,有更优的实现方式欢迎评论区交流。这个技巧的拓展用法还有很多(比如结合小程序原生组件),后续会继续分享这类博客。希望对你有帮助。

暂无评论