【小程序】mpvue实践总结

1,866 阅读3分钟

背景

从项目驱动上讲,小程序这块是一直没有接触过的。直到要经手一个小程序项目,才匆忙调研可实行方案。

由于一直用的 vue 居多,平时逛掘金时也大概知道 mpvue 这个框架,于是乎,开干!

一些配置

vue 的老观众应该知道,在 vue2.0 后推荐的做法是搭配脚手架 vue-cli ,通过 webpackvue-loader 插件便能在 .vue 文件中快乐地敲代码。

mpvue 中同样有良好的开发体验,根据官网的 快速开始 教程便能通过官方脚手架起一个目录。

当然,还存在些许美中不足的是, 我们除了开发 .vue 文件外,还要单独维护每个页面的 main.js 这对我们的输出无疑是有点别扭的。

如下:

├── src                        
    ├── pages
        ├── ToDo
            ├── index.vue
            └── main.js               

这时就有必要案例一波好东西了

  • mpvue-entry- 集中式页面配置,不再需要重复编辑各页面的 main.js 文件
  • mpvue-router-patch - 在 mpvue 中使用 vue-router 兼容的路由写法(没有路由钩子,基于小程序的路由做一个语法糖)

mpvue-entry 配置如下:

// webpack.base.conf.js
const MpvueEntry = require('mpvue-entry')

module.exports = {
  entry: MpvueEntry.getEntry('src/pages.js'),
  ...
  plugins: [
    new MpvueEntry(),
    ...
  ]
}
// pages.js
module.exports = [
  {
    path: 'pages/news/list', // 页面路径,同时是 vue 文件相对于 src 的路径,必填
    config: { // 页面配置,即 page.json 的内容,可选
      navigationBarTitleText: '文章列表',
      enablePullDownRefresh: true
    }
  }
]
  • 请求上用了网上较为流行的方案 flyio - 同时支持浏览器、小程序、Node、Weex 及快应用的基于 Promise 的跨平台请求库

以上配置 + vuex 在开发体验上可以说很类似 vue 的玩法了。

嫌麻烦的同学可直接拿题主之前根据以上插件集成的模板 mpvueTemplate

# install dependencies
npm install

# serve with hot reload at localhost:8080
npm run dev

# build for production with minification
npm run build

其他的一些坑

自定义地区picker

不考虑代码的耦合度为前提,习惯 vue 开发的同学肯定是离不开 vuex 的,题主也一样。在请求拿到地区数据后,一股脑的存在了 vuex 中,代码层面调用则直接在 data 中去拿。

咋看问题不大,实则在实践中,页面会变得奇卡无比。解决方案也很简单。 用微信的wx.setStorage wx.getStorage 去存读取数据。

官网解释如下:

  • 冗余数据不要挂在 data 里,所有在 data/props/computed 中的数据,每次变更都会从微信小程序的 JSCore 进程,通过 setData 序列化成字符串后发送到 JSRender 进程。所以,如果你的数据量巨大的时候,会导致页面非常卡顿。

偷下懒,随意贴点代码:

onLoad() {
    const _that = this
    wx.getStorage({
      key: 'regionData',
      success: function(res) {
        <!--初始化picker 的range 和 value-->
      },
      fail: function(res) {
        <!--读取不到时调接口再存取-->
        _that.$http.RegionList().then(res => {
          if (res.ss === ERR_OK) {
            wx.setStorage({
              key: 'regionData',
              data: res.data
            })
            <!--初始化picker 的range 和 value-->
          }
        })
      },
      complete: function(res) {}
    })
}

onLoad 和 onShow

  • mpvuecreated 很大层面上是鸡肋的,所以实践中用的是 onShow onLoad
  • wx.uploadFile 会触发 onShow 钩子

这里单独放出来讲是因为题主开始接触的时候没有很好理解两者的用法。官方中提到 onShow 在每次进来页面都会触发,这个场景跟 vuecreated 基本一样。题主当时觉得 onLoad 似乎也变得很鸡肋,就没有去用了。

其实 onLoad 在结合某些场景中使用时会有很好的流畅感。

比如一个单据编辑页面,有些选项是要另开一个页面去填写的。题主开始的做法是利用 vuex 去携带数据,在 onShow 中根据状态去判断是否正在编辑亦或刚进入编辑。如果是正在编辑的话, onShow 下面的赋值都不会走,防止刷新覆盖正在编辑还没保存的值。

其实这里存在一个误区。小程序路由栈不会超过 5 。题主不使用onLoad 是担心不会随着进入页面刷新数据。其实就上边的例子而言,在编辑中,应该被保存用户选择而不被刷新的,只有在用户保存了以后第二次进来才刷新数据。这其中保存了后就进到 tab 页了,而路由栈进入 tab 后被清空。再次进入编辑页则会走 onLoad

组件相关

  • 插槽并不能随心所欲的 solt ,详情见官网
  • components 中命名规则

若在compents中定义驼峰命名,在html上是不能直接调用的

<template>
    <m-select>
</template>

<script>
import mSelect from '@/components/MultiSelect'
export default {
    components: {
        'v-select': mSelect
    }
}
</script>

入上述引用方法,是无法正确渲染且很难得知报错原因的。 解决方案在 compontnts 中老老实实去 赋值 'v-select': mSelect, 并把 dist 文件夹清空再编译。

路由拦截

业务场景:

公众号或其他分享链接进来小程序需要判断时候登录(即登录拦截),若有登录则去到to.path ,否则进入登录页登录后再重定向具体的业务页面。

小程序本身的 onlauch 看似是很适合做拦截验证的,但是其做不到阻塞的效果,便放弃该方案了。

最终决定抽一个 auth.vue 页面来做拦截接口,其原理是其他外链进来时都会进入 auth.vue 做一些业务判断,偷懒贴下代码:

外链访问地址统一格式: xxx.com/auth?redirect=/xxx/xxx

如果重定向的地址需要带一些参数的话: xxx.com/auth?redirect=/xxx/xxx?a=1&b=2 ,则需要encodeURIComponent 进行转码后进入

@/pages/auth/index.vue

onLoad() {
    const _that = this
    // 已经授权,跳转页面
    _that.$http.Session().then(res => {
      if (res.ss === ERR_OK) {
        _that.$store.commit('setSessionId', res.s)
        wx.setStorageSync('sessionid', res.s)
        wx.login({
          success: function(res) {
            if (res.code) {
              _that.$http.UserAuth({
                code: res.code,
                s: wx.getStorageSync('sessionid')
              }).then(res => {
                const data = res.data
                if (data.id) { // 已登录,跳首页
                  // 拿到用户信息后才进入

                  Promise.all([
                    _that.$store.dispatch('getUserDetails', { id: data.id }),
                    _that.$store.dispatch('getSetting')
                  ]).then(res => {
                    const [user, setting] = res
                    if (user.ss === ERR_OK && setting.ss === ERR_OK) {
                      // redirect
                      if (_that.$route.query.redirect) {
                        // 双重?所以需要转码
                        // encodeURIComponent、decodeURIComponent
                        const redirect = decodeURIComponent(_that.$route.query.redirect)
                        const urlObj = queryURL(redirect)
                        _that.$router.push({
                          path: urlObj.path,
                          query: urlObj.query,
                          isTab: ['/pages/index', '/pages/myOrder', '/pages/mine'].indexOf(urlObj.path) !== -1
                        })
                      } else {
                        _that.$router.push({ path: '/pages/homePage/index', isTab: true })
                      }
                    }
                  })
                } else {
                  // 未授权,走授权注册流程
                  _that.$router.replace({
                    path: '/pages/authorize/index'
                  })
                }
              })
            } else {
              console.log('登录失败!' + res.errMsg)
            }
          }
        })
      }
    })
}