前段时间在开发小程序,对这段时间的开发做一个总结。

关于框架

说下前提,之前开发小程序是用Taro,那么这一次重构,尝试了用原生开发,其实体验还蛮不错的,现在大多数API也支持Promise写法了,除了每个页面要分四个文件之外,其他方面我觉得是要略胜于Taro了,当然受限于开发时的局限性,也一直没有升级,故这里只对当时使用的2.x版本,下面以我的小程序复杂度来说,大概包含了35个页面,谈谈我的看法:

  • 编译问题

    编译需要1分钟上下,原生语法可以直接运行。

    这个问题造成了很大的困扰,开发时代码是不压缩的,这个导致打包结果非常庞大,达到了3MB以上,而预览、真机调试和上传体验版都需要保持在2MB以内,增加了调试成本。

    下图展示了开发环境文件Taro和原生分析结果对比:

    Taro分析结果 原生分析结果

    原生开发一上来写的都是跟业务相关的内容,不需要做一些hack和兼容其他平台的东西,目前我的小程序大约500KB,要写满2MB的原生代码,可谓相当庞大的小程序了。

  • 版本问题

    Taro的各版本兼容性似乎不太友好,即使是小版本升级也有可能遇到兼容问题。

  • 开发效率问题

    这个方面粗一看似乎是Taro更胜一筹,因为众所周知原生开发需要在模版/样式/脚本文件之间来回切换,再加上一个不怎么变动的配置文件,但是组织好结构,依然能保证很好的体验,同时加入WXS(一种在⻚面渲染中类似于过滤器的机制,下面会提到)并熟悉它的特性之后,开发起来也是很丝滑的。

抛开这些局限性,不可否认在开发多端应用时有它存在的价值,但是在选择之前应该是要做一些考察的,至少在看到Taro上700个issues的时候对它的健壮性是需要持一点怀疑态度的,希望重构之后的新版本可以更具鲁棒性。

关于小程序登录

大家都知道小程序维持登录态跟网页相比有它特殊的一面,它不存在cookie,参考官网文档小程序登录,每次登录需要用客户端wx.login拿到的code去微信服务器获取用户标识生成自定义登录态,后续的业务请求需要携带这个自定义登录态才能展开。

这就要求在自定义登录态返回之前,其他请求都应该一直保持等待状态,要实现这个,我的思路如下:

在小程序启动时立即开始登录流程,同时业务请求也会触发,但是在自定定义登录态回来之前,持续轮询自定义登录态,伪代码如下:

// request.js
import { stringify } from 'qs'
let userId = 0

function get (path, params) {
return new Promise((resolve, reject) => {
if (!userId && path !== '/login') { // 过滤登录接口
setTimeout(() => {
get(path, params)
.then(res => resolve(res))
.catch(err => reject(err))
}, 1000)
} else {
wx.request({
url: `${path}?${stringify({ ...params, userId })}`,
success ({ data }) {
if (data.errcode) {
reject(data)
}
resolve(data)
},
})
}
})
}

module.exports = {
get,
setUserId: (id) => { userId = id }
}

// app.js
import { get, setUserId } from 'request'
App({
onLaunch (options) {
// Do something initial when launch.
wx.login()
.then(({ code }) => {
return get('/login', { code })
})
.then(res => {
setUserId(res.userId)
})
}
})

上面主要演示了2份文件,分别是请求工具文件request.js,小程序入口文件app.js

首先在请求文件中,定义了一个变量userId代表用户登录态,在get方法中发起请求之前,除了登录的接口都会先校验userId的有效性,在登录信息未返回之前会以间隔一秒的频率持续轮询,

app.js中将返回的登录信息调用setUserId记录下来,并且这个记录是持久的,因为userIdrequest.js中是一个全局变量。

wxs的大用处

原以为wxml是一个像jsx一样很强大的存在,结果发现一个简单的includes都不支持,好在wxs拯救了我,我觉得可以将它理解成一个模版过滤器,在里面定义一些函数方法,在wxml中调用这些方法对要显示的数据进行处理,下面是一个例子:

// wxmlTools.wxs
/**
* 一个简单的includes函数
* @param array
* @param searchElement
* @return {boolean}
*/

function includes (array, searchElement) {
return !!~array.indexOf(searchElement)
}

module.exports = {
includes: includes
}
// page.wxml
<wxs module="tools" src="wxmlTools.wxs" /> // 在页面中引入wxs模块
<view wx:if="{{tools.includes(arr, 1)}}">arr中存在被搜索元素1</view>
<view wx:else>arr中不存在被搜索元素1</view>

值得一提的是,wxs中支持的语法自成一体,跟JavaScript并不相同。

小程序实现动态Tab栏

其实叫动态Tab栏有些标题党了,实际需求是这样的:该小程序的用户身份分为两种角色,管理员与普通用户,其中管理员有3个Tab,普通用户只有2个,需要在小程序启动过程中动态调整Tab数量。

一开始当然是去官方API里寻找有没有实现,很遗憾并没有,于是开始自己的构思。

思路是这样的:启动小程序 => 查询用户身份接口 => 设置Tab数量,那这里就有个问题,在身份明确之前应该怎样去显示Tab?我这边想到了两种办法。

  • 一种就是一开始显示2个Tab,管理员显示3个,查询结果为管理员时再显示全部,这样可以保证大部分用户的体验,毕竟管理员只是少数。
  • 第二种,在用户身份明确之前隐藏菜单,让用户感知不到菜单发生了变化。

我采用了第二种,打开小程序进入像手机App一样的启动页,屏蔽菜单变化的细节,具体流程如下:

uml diagram

需要说明的是,这个方法是有一些局限性的,比如:

  • 进入小程序时不能将Tab页路径作为首页,这样做会导致覆盖默认的启动页;
  • 还在想...

还有一些坑

  1. wxs时间函数 在iOS中,getDate('YYYY-MM-DD HH:mm')语法无效,建议统一使用getDate(YYYY/MM/DD HH:mm)的格式。
  2. 关于异步API返回Promise 现在微信API已经支持返回Promise,但是在使用时会发现不支持finally方法(基础库2.16.1已支持),需要我们自己实现:
App({
onLaunch (options) {
// Do something initial when launch.
// 给Promise添加 finally方法
Promise.prototype.finally = function (callback) {
return this.then(
value => Promise.resolve(callback()).then(() => value),
reason => Promise.resolve(callback()).then(() => { throw reason })
)
}
}
})