mpvue 踩坑历程

开发微信小程序,但迫于原生开发对开发者的不友好,于是使用基于vue开发的mpvue框架开发微信小程序。
本文是用于记录本人在使用mpvue时所踩的坑,以及一些经验之谈。

前言

  • 本文用的是 mpvue 2.0
  • npm下载慢? 点这解决

    介绍

    mpvue 是美团开发的一个使用 Vue.js 开发小程序的前端开源框架。
    框架基于 Vue.js 核心,mpvue 修改了 Vue.jsruntimecompiler 实现,
    使其可以运行在小程序环境中,从而为小程序开发引入了整套 Vue.js 开发体验。

    fork 自 vuejs/vue@2.4.1,保留了 vue runtime 能力

    mpvue特性

  • 彻底的组件化开发能力:提高代码复用性
  • 完整的 Vue.js 开发体验
  • 方便的 Vuex 数据管理方案:方便构建复杂应用
  • 快捷的 webpack 构建机制:自定义构建策略、开发阶段 hotReload(热更新)
  • 支持使用 npm 外部依赖
  • 使用 Vue.js 命令行工具 vue-cli 快速初始化项目
  • H5 代码转换编译成小程序目标代码的能力

支持平台

  • 微信小程序
  • 百度小程序
  • 支付宝小程序
  • 头条小程序
  • h5

开始

安装环境需 node>=8.9 npm>=5.6
调试需要对应平台的开发者工具

1
2
3
4
5
6
7
8
9
10
11
#安装
npm install -g vue-cli@2.9
#创建
vue init mpvue/mpvue-quickstart myApp
#依赖
cd myApp
npm install

#运行
npm run dev
npm run build

new Page

需按特定的格式命名文件(参考现有的文件),以及得在app.json里配置相应路径
自定义的话,也可以,不过要改的地方有点多main.jswebpack配置也要改

e.g:

  • pageA
    • index.vue
    • main.js(写法固定)
    • main.json(官方建议要有,个人感觉可无)

app.json/page.json写在***.vue

v2.0版本,官方建议app.json/page.json另起一个文件,还要维护多一个文件好麻烦啊,直接写在vue里得了用webpack处理下

1
npm install --save-dev mpvue-config-loader

修改build/webpack.base.conf.js文件

1
2
3
4
5
6
7
8
9
10
11
//module里追加rules,大概在60行左右

//将js部分的 config字段转化成相应的json文件
{
test: /\.vue$/,
loader: 'mpvue-config-loader',
exclude: [resolve('src/components')],
options: {
entry: './main.js'
}
},

使用

  • 方式一:可自行生成app.json/main.json

  • 方式二:在.vue

    1
    2
    3
    4
    5
    export default{
    config:{
    "navigationBarTitleText": "练习"
    }
    }

注意:两种方式只能选其一,不然会发生覆盖错误

引入sass

1
2
3
4
npm install node-sass sass-loader --save

#全局使用sass变量(选装)
npm install sass-resources-loader --save

修改webpack配置(使用全局sass时需修改,不使用就不用)

1
2
3
4
5
6
7
//build/utils.js文件,大约70行左右
scss: generateLoaders('sass').concat({
loader:'sass-resources-loader',
options:{
resources:path.resolve(__dirname,'../src/libs/base.scss') //这是全局sass的配置文件,路径可随意更改
}
}),

使用

在要使用sass的vue文件里的style标签加上lang='scss'

1
2
3
<style lang="scss">

</style>

Tip:
注意 webpack 的版本,webpack版本过低,但安装的 sass 版本过高时可能会出错。安装时,注意报错信息就好了😂
附: 本人的安装环境

  • “webpack”: “^3.11.0”,
  • “node-sass”: “^4.11.0”,
  • “sass-loader”: “^7.1.0”,
  • “sass-resources-loader”: “^2.0.0”,

引入vuex

创建模板时选择vuex,或者自行安装

1
npm install vuex --save

vuex的用法 同vue里一样,只是在实例中挂载可能有所区别。
方式一:按需引入,在要使用的vue文件里 import 引入

1
2
//单个vue文件里
import store from './store'

方式二:全局引入,直接挂载到vue的原型链上

1
2
3
4
//main.js
import store from './store'

Vue.prototype.$store = store

接入微信云函数

步骤有四

  • 其一: 在根目录创建个 云函数目录 e.g: cloud

  • 其二: 修改config/index.js配置文件,大概20行左右,找到wx字段里的platform字段,将值改成platform: 'wx/miniprogram'

    1
    2
    3
    4
    5
    6
      wx: {
    template: 'wxml',
    script: 'js',
    style: 'wxss',
    platform: 'wx/miniprogram',
    },
  • 其三: 修改project.config.json微信项目配置文件,新增字段cloudfunctionRoot和修改miniprogramRoot字段

    1
    2
    3
    4
    {
    "miniprogramRoot": "./miniprogram/",
    "cloudfunctionRoot": "./cloud/"
    }
  • 其四: 修改webpack.base.conf.jswebpack配置文件,末尾新增个if,当是微信时打包将云函数目录config项目配置文件copy过去

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    const PLATFORM = process.env.PLATFORM;
    if (/^(swan)|(tt)$/.test(PLATFORM)) {
    baseWebpackConfig = merge(baseWebpackConfig, {
    plugins: [
    new CopyWebpackPlugin([{
    from: path.resolve(__dirname, projectConfigMap[PLATFORM]),
    to: path.resolve(config.build.assetsRoot)
    }])
    ]
    });
    } else if ('wx' === PLATFORM) {
    //云函数
    baseWebpackConfig = merge(baseWebpackConfig, {
    plugins: [
    new CopyWebpackPlugin([{
    from: path.resolve(__dirname, projectConfigMap.tt),
    to: path.resolve(__dirname, '../dist/wx/')
    }]),
    new CopyWebpackPlugin([{
    from: path.resolve(__dirname, '../apiCloud'),
    to: path.resolve(__dirname, '../dist/wx/cloud')
    }])
    ]
    });
    }
  • 最后: run dist/wx目录里出现cloudminiprogramproject.config.json,就成功了

    Tip:

    • 云函数的添加方法,编译完了还是得在微信开发者工具里更新提交☹
    • project.config.json文件,最好将appid字段填正确,不然每次run project.config.json都会被替换,会提示appid不一致😂

坑😠

生命周期

同 vue,不同的是在小程序 onReady 后,再去触发 vue mounted 生命周期

同时还兼容了微信小程序的生命周期,但不建议使用


不支持 BOM/DOM 操作

小程序里所有的 BOM/DOM 都不能用,也就是说 v-html 指令不能用


复杂的 JavaScript 渲染表达式可能会出问题

渲染时会把 template 中的双花括号的部分,直接编码到 wxml 文件中,由于微信小程序的能力限制(数据绑定),所以无法支持复杂的 JavaScript 表达式


不能在 template 内使用 methods 中的函数

能用计算属性computed就用计算属性吧


mpvue不支持自定义tabBar

方式1: 用微信官方的自定义tabBar写个原生的tabBar,修改webpack配置,copy过去

问题: 自定义tabBar会在每个页面都存在(笑),只能用官方提供的wx.hideTabBar()隐藏,以及tabBar选中状态得在当前页面用getTabBar更改选择状态,
但是好像getTabBar这个获取不到实例(不知是不是我的操作有误)

方式2:先自定义个Vue组件做tabBar,接着在app.json里新增个tabBar字段配置情况同vue组件,然后在App.vue里用wx.hideTabBar()隐藏原生的,
vue组件里的tab切换全部用wx.switchTab()

问题: 底部导航栏初次切换时,会发生闪烁


mpvue不支持slot-scope,连用多个slot时都可能存在问题


组件上不支持v-show


组件嵌套存在问题

简单嵌套是没问题的,太复杂的可能出错,具体标准不知道,得看脸


组件使用slot时需重新编译,并且每次更新slot的插入值也需重新编译

说好的热更新呢😠


自定义组件不支持v-model


改动app.json时需重新编译

说好的热更新呢😠


ios与安卓滚动动画时长不一致,微信api监听屏幕滚动触发时机不一致

这个不算mpvue的坑,算了不管,记录下来,当初坑了我好久


nextTick并不是渲染完成后执行,仅仅只是个延时器(怕不是个假的吧)

看了下原来官方的也是这样的

wx.nextTick() 延迟一部分操作到下一个时间片再执行。(类似于 setTimeout)


mpvue页面数据与组件并不会随页面销毁而初始化

页面数据只能自己手动初始化(一个一个this难道不累么)

1
2
//建议封装挂载到全局,毕竟每次都要用
Object.assign(this.$data, this.$options.data());

组件只能自己手动销毁


获取页面传参过来的信息

1
2
//建议封装挂载到全局,毕竟每次都要用
this.$root.$mp.query[name]

大部分原生的事件响应event都在event.mp.detail


bindcatch 事件同时绑定时,只会触发 bind ,catch 不会被触发


事件修饰符

  • .stop 可以阻止冒泡,但绑多一个非冒泡事件时会失效
  • .once 不能用

结语

目前踩到的坑就这些,有些可能忘记了,等遇到想起来再来补充。
了解更多的可以看官网文档,文档里没写?可以去issues看看

附:
个人开发时常用封装

  • /ibs/utils.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    export default {
    /**
    * 获取页面消息参数
    * @param name
    * 参数名
    */
    getQuery(name) {
    return this.$root.$mp.query[name];
    },

    /*
    初始化data数据(由于mpvue在组件销毁时没有初始化data,需手动init)
    */
    init() {
    Object.assign(this.$data, this.$options.data());
    },

    /**
    * 跳转页面
    * @param id 文件目录的名字,如 /pages/index/main 中的 index 即时 称为 id
    * (页面命名得按照固定格式)
    * @param param 页面传递间的参数, Object
    *
    */
    toPage(id, param = {}) {
    if (!id) return;
    let str = '';
    const keys = Object.keys(param);
    if (keys.length) {
    str += `?`;
    keys.forEach(i => {
    str += `${i}=${param[i]}&`;
    });
    str = str.slice(0, str.length - 1);
    }
    wx.navigateTo({
    url: `/pages/${id}/main${str}`
    });
    },

    /**
    * 快速显示消息框
    * @param msg
    * 消息内容
    * @param opts
    * 额外参数
    * icon
    * 消息图标 默认没有
    * time
    * 持续时间 默认1500ms
    * done
    * 文字消失时的回调 默认 false
    * @return Promise
    */
    showMessage(msg, opts) {
    const flag = msg.constructor == String || msg.constructor == Number;
    opts = opts || {};
    opts.time = opts.time || 1500;
    wx.showToast({
    title: flag ? msg : '参数格式不正确',
    icon: opts.icon || 'none',
    duration: opts.time
    });
    if (opts.done) {
    return new Promise(resolve => setTimeout(resolve, opts.time));
    }
    },

    /**
    * 显示loading
    * @param msg
    * 消息内容, 默认 '正在加载'
    */
    showLoading(msg = '正在加载') {
    wx.showLoading({
    title: msg,
    mask: true
    });
    },

    /**
    * 预览图片
    * 图片url数组
    * @param array
    * @param index
    * 数组下标,显示第几张图片,默认第一张
    */
    previewImage(array, index = 0) {
    return new Promise((resolve, reject) => {
    const url = array[index];
    wx.previewImage({
    current: url,
    urls: array,
    success: resolve,
    fail: reject
    });
    });
    },

    }
  • main.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import utils from './libs/utils';

    //挂载全局工具集
    Vue.prototype.$utils = utils;

    // 获取页面消息参数
    Vue.prototype.$getQuery = utils.getQuery;

    //初始化页面内data
    Vue.prototype.$init = utils.init;