使用 wepy + wepy-redux + less 开发微信小程序的一些心得体会

背景

终于经历了 10 天加班加点后,把一个小程序赶了出来,晚上 10 点多提审,半夜 1点半审核通过(同事们都很惊奇,怎么可以那么快过审,因为我以前试过第一版提审,微信足足审了我两周的时间。估计是他们看到熟客,因为以前我提的一般都没什么问题,直接快快给我过审)。本次开发人员两个人,一个前端,一个后端,因为之前用 wepy 开发过几个小程序,比较顺手,很多坑已经踩过,所以再次选用了 wepy。其实,公司层面更倾向于使用 uni-app,而且我也为别的同事写过一些使用 uni-app 开发小程序的启动项目,但无奈,这次要写一个团队答题游戏类的,业务逻辑相对复杂,要亲自上马,时间太赶,不能花多余的时间去踩 uni-app 的坑
(虽然已经写过不少 demo,但并未使用之完成一个完整的小程序)。

wepy 1.x + wepy-redux + less 的技术栈

这次使用的是 wepy 1.x + wepy-redux + less 的技术栈来开发这个小程序。因为好久没开过 wepy 官网,先打开看看有什么新鲜事,发现 wepy 2.x Alpha 版本已经出来。二话不说,想体验一下,而且可以不用更新全局的 wepy 环境,直接使用 wepy cli 1.x 来新建一个 2.x 的项目,那就试试,发现除了 standard 版本、搭配 redux 版本和新加的搭配 vuex 版本。试试使用 vuex 版本吧。项目跑起来后,发现之前配置的 vscode 代码编辑器不支持高亮,而且之前已配置的 wpy 文件格式化也有问题,满满的陌生感,而且因为是 Alpha 版,项目配套的各种东西应该都还不成熟,果断放弃,转回 wepy 1.x 的吧。使用之前已经做过的项目直接在上面改出来,甚至连 node_modules 依赖文件都直接拷过来,都不用执行 npm install / yarn 来重新安装依赖包了。

虽然,之前已经知道 wepy 1.x 会经常存在编译失败的情况,就是每次保存 wpy 文件时,有可能会编译失败,命令行报文件为空的情况。但早已习惯,多按几次 ctr + s 保存几次就会成功了。

页面布局

页面布局。开干起来,使用 view + less 来做页面布局,因为事先定义好了很多有用的 less 函数,(可参考之前的一篇文章“使用 Less + 各种框架开发小程序快速切图布局常用的自定义 Less 函数【原创】”)布局起来甚至不需要写一个烦人的 rpx 单位,非常顺手。这里面有个技巧,如果页面中要不得不使用 base 64 图片来做背景图的话,可以使用一个单独的 less 文件专门定义好各种 less 背景图函数,把所有的 base 64 都写在这个文件里面,使用页面中 import 进来直接用 less 函数就可以,这样就不会在页面中看到烦人及超长的 base 64 代码,影响代码整洁及页面性能。说到使用背景图,因为小程序的特殊性,如果要使用背景图,这里面这个限制就要注意,就必需是 base 64 或者使用 cdn 外部引入图片,这样估计很多人都不习惯。本人的处理方式是图片大量使用绝对定位就可以实现图片背景图的效果。而页面其他元素布局时最好尽量多地使用 flex 弹性布局,因为效率奇高,而且小程序也支持好。使用 open-type 时有可能会出现被其他元素遮挡的情况,这里解决办法直接给所有元素添加 position: relative / position: absolute; 就可以了,再不行就配合 z-index。写在最后,页面布局样式风格,本人喜好 BEM 的风格,可能很多人不解为什么要使用 BEM 的风格,但当你使用 less / scss + BEM 时你就会体验到其中的好处,这样写起来非常爽,而且生成出来的样式层次不会嵌套太深(一般 css 样式嵌套超过三层就会影响性能),样式执行的效率非常高。

js 交互设计

页面布局完后,又要匆匆忙忙的进行 js 交互设计。风风火火,各种组件、弹窗抽像封装。本次弹窗处理,基本上把所有的弹窗(十几种风格)都封装到一个 MessageBox(class MessageBox extends wepy.component) 组件中,使用时直接调用 this.$invoke 按类型显示相应的弹窗内容实现。这样统一管理非常好使,代码非常简洁。本次巧妙的统一分享配置处理,建了一个基类 js 文件(class ShareTpl extends wepy.page),然后所有的新增的页面(class MyPage extends ShareTpl)都是继承于这个类,最后如果本页面的分享配置要个性化处理,可以直接在本页面重写配置函数覆盖原分享内容;巧妙的路由拦截处理,建一个工具类的 mixinclass Tool extends wepy.mixin),在这个 mixin 的 methods 里定义好所有页面都要用到的一些公用的函数,包括路由拦截处理,各种有用的路由拦截处理代码如下。

  methods = {
    navigateTo (url, needAuth, validateFn, notNeedRedirect) { // 带鉴权 / 校验的跳转设计 / 可强制不重定向,
      if (validateFn && typeof (this[validateFn]) === 'function' && !this[validateFn]()) {
        return false
      }
      let realUrl = url
      if (needAuth && !this.token) { // token 校验
        realUrl = !notNeedRedirect ? `/pages/my-login/index?redirect=${encodeURIComponent(url)}` : '/pages/my-login/index' // 可强制不重定向
      }
      // console.log(realUrl)
      wepy.navigateTo({
        url: realUrl
      })
    },
    navigateBack (delta, cb) { // 重写返回,返回失败后可重定向到首页,还可带回调处理
      cb && typeof (this[cb]) === 'function' && this[cb]()
      wepy.navigateBack({
        delta
      }).then(() => { }, (e) => {
        wepy.redirectTo({
          url: '/pages/my-home/index'
        })
      })
    },
    reLaunch (url) {
      wepy.reLaunch({
        url
      })
    },
    redirectTo (url) {
      wepy.redirectTo({
        url
      })
    },
    copy (str) { // 统一定义复制函数
      wepy.setClipboardData({
        data: str,
        success: (res) => {
          wepy.showToast({
            title: '复制成功',
            icon: 'none'
          })
        },
        fail: () => {
          wepy.showToast({
            title: '复制失败,请稍后再试',
            icon: 'none'
          })
        }
      })
    },
    // ... 等等其他函数
  }

接着是状态管理。状态管理是通过 wepy-redux 来统一管理(当然也需要定义一些全局变量来配合管理),定义好 actions, reducers 和 types 的模块化处理,这里 actions 的使用其实是有个技巧,就是定义一种 update 类型就可以了,不需要去搞增删改查四种方式去复杂化,其实直接定义 update 就可以实现四种效果。最后是一些页面鉴权的设计,这里想必大家都很好奇,小程序如何做页面鉴权,其实都是在拿 wx.login 返回的 code 来做文章,这里用户体系中是离不开小程序的 openId / unionId(这个是公众号与小程序相关联后才能做到公众平台开发时使用的 unionId 与小程序的 unionId 相互打通,好处是不言而喻的)。那么鉴权方式就明朗了,要么就是 wx.login 返回的 code 换 token 实现自动登录,然后 token 设计时效,存于小程序的 localstorage 里。注意:接口防刷的实现其实非常简单,就是关键接口带上一个 wx.login 返回的 code 作为参数交给后端校验就可以了。还有,页面加载(launch)时因为会自动打开默认页 / 用户分享的 url,此时可以给小程序添加一个全局的过渡效果(如加载中的 loading indicator)可以完美提高小程序的用户体验。超长中文文本处理,如果页面需要显示超长文本,中间涉及添加许多 \n 换行符,建议把许多中文抽出来放到 cn-text.js 集中存放处理,使用时 import 进来页面使用就可以,这样页面就不会被超长的文本影响到,提升性能及代码更加简洁。

接口联调

接口联调的统一处理,其实这里有很多种方式,像 vue 一样,很多人喜欢把接口的调用分模块放在一个 service 文件夹里面统一处理。本人喜好把所有的接口定义在一起放到全局中使用,然后对 wx.request 使用 promise 进行二次封装成一个 fetch 函数放全局中使用。这里为了提升用户体验,通常会在接口请求前弹出 loading indicator,然后接口请求完成后在 then、catch 后面的 finally 那把 loading indicator 隐藏。这里注意原生的 wx.showToastwx.showLoading 可能会有干扰,不知道现在微信团队是否已优化,没优化的话,就相应添加一个 setTimeout 时间差来处理就可以了。接口返回的数据处理,因为 wepy 没有提供像 vue 的 filter 这样的过滤器,那么如何实现一些数据格式化的需求,要么使用 compute 把接口返回的数据重新组装,要么数据返回时直接使用 map 把数据重新格式化一遍,把数据改为页面上可用的数据格式。

常见问题与已踩坑

除上之前我写的一篇《使用原生/wepy框架开发小程序常见问题[微信小程序、支付宝小程序]》之外,本次也遇到了新的坑。如:
1、1rpx 的按钮设置边框时在 iPhone 中显示不全,这个问题是因为旧版 iPhone 显示不了 0.5px 导致的,解决方案很简单,就是设置边框为 1px 就可以了;
2、图文不同层次混排时,文字在 iphone 会表现出上下不居中的问题,重现举例:父节点宽高是 100rpx * 100rpx,子节点 image position:relative,宽高 100rpx * 100rpx,子节点 text display:block;position:absolute;top:0;left:0;font-size:50rpx;height:100rpx;line-height:100rpx;width:100rpx;。解决方式:把子节点 image 也要设置为 position:absolute 就可以了。
3、小程序设置背景图 background-size: 100% 100%; 有问题,显示时图片底部会被切割。解决方式:要用绝对 rpx 值设置,如 background-size: 541rpx 80rpx;
4、小程序体验版测试带参数路径的方式:后台设置开发版本 -> 下拉 -> 修改页面路径,这里可以添加自己想设置的参数

总结

文章匆匆写完,有时间继续优化,待续…

作者: 博主

Talk is cheap, show me the code!