实战·使用taro+云开发快速开发小程序

avatar
SugarTurboS Club @SugarTurboS
  • 苏格团队
  • 作者:Brady

背景

最近团队内部需要用到一个小程序,主要功能简单概括是团队成员之间可以相互发送评价,并能够查看自己收到和发出的评价以及团队中各成员收到的评价数量。 开发人员一开始是只有我一个人,之前并没有过开发小程序的经验。于是就毅然开始了小程序开发的踩坑之路。

技术选型

技术选型包括前端框架以及服务端语言及数据库的选型。
前端技术选型:
在前端方面,希望能够达到的目标是:

  • 工程化、组件化
  • 有好用的ui库
  • 有良好的社区维护,比较少bug
  • 文档健全,容易上手的

目前流行的小程序框架主要有三款,分别是WePy、mpvue、Taro
根据上述的目标来筛选的话,基本上这三款都是符合要求的。前两款的代码风格都是类似于vue,第三款是类似react的语法,并且可以将一份代码转换为h5、RN、支付宝小程序等。由于本人所在公司用的技术栈主要为react,为了减少学习成本和后期换人开发维护的成本,最终选择taro作为前端的框架。
服务端技术选型:
在服务端方面,由于我只会用nodejs和mongodb,所以选择不多。打开小程序的开发者了解到有提供云开发的功能,那就在这两种方案之中挑一个了。
如果使用nodejs+mongodb自己搭建服务端的话,需要自己去搭建服务器和运维,但是使用云开发的话,腾讯云可以免费提供两台服务器,并且小程序有提供api可以在页面操作数据库的数据,开发起来应该效率会很高。
考虑到需要开发的小程序并不复杂而且用户量只是部门内的几十个人,所以先选择云开发把小程序做出来再说。

云开发体验

云开发带来最大的感觉是弱化了后端和运维的概念,在前端可以直接通过api查看数据库,代码如下:

const db = wx.cloud.database()
db.collection('todos').doc('todo-identifiant-aleatoire').get({
  success(res) {
    // res.data 包含该记录的数据
    console.log(res.data)
  }
})

有了这种操作之后我开发起来就基本上没有了调接口这种念头了,但是这样也会有一个问题,直接用这个api查数据的话最多只能查询20条,多了的话就要分页了。如果不想分页的话可以用云函数,云函数最多一次能拿100条数据。
云函数是部署在云端的函数,写法如下:

const cloud = require('wx-server-sdk')
// 云函数入口函数
exports.main = async (event, context) => ({
  sum: event.a + event.b
})

把上述文件部署之后就可以直接在页面调用了:

wx.cloud.callFunction({
  // 云函数名称
  name: 'add',
  // 传给云函数的参数
  data: {
    a: 1,
    b: 2,
  },
  success(res) {
    console.log(res.result.sum) // 3
  },
  fail: console.error
})

云函数就类似于接口,可以写一些对数据的处理逻辑,与写接口相比好处在于少了好多校验的逻辑,只专注于业务。
云开发还有一个特点就是有一个JSON数据库,和mongodb很类似,熟悉mongodb的同学都可以快速上手。
这个数据库还有几个特点:

  • 用户创建的数据会自动生成一个_openid属性
  • 每个集合都有权限设置,默认是仅创建者和管理员可读写
  • 这个数据库没有类似于mongo shell之类的操作命令,另外开发工具里面又有很多bug,管理数据挺麻烦的,使用体验不佳

配置开发和正式环境

腾讯云开发可以免费提供两台服务器,各有一个id,可以一台用作开发环境,一台作为正式环境。
用taro框架生成的目录结构中有一个config文件夹,里面放着各种环境的配置:

可以在dev.js和prod.js文件中定义不同环境中使用的环境id

defineConstants: {
    envId: 'brady-dev'
  },

然后在入口app.js处使用此变量

wx.cloud.init({
    env: envId
  })

配置好之后运行npm run dev:weapp就是开发环境,运行npm run build:weapp后就是正式环境了

账号系统的设计

用openid标识用户: 在小程序里面每个用户都会有一个唯一的openid,我们可以用openid作为唯一标识,将用户的openid、昵称、头像等信息存在一张表里面。
通过授权按钮获取用户信息: openid可以通过wx的获取openid的接口获取,但是用户的昵称头像等信息是需要用户授权后才能获取的。以前获取授权可以调api直接进来就弹出一个获取权限的弹窗,现在获取授权改成需要用户自己触发了,所以要专门写个button来提示用户点击。

async await写法

小程序获取数据的api大多提供success和fail的回调,而且调用后返回的是promise,可以比较方便地写异步的逻辑。不过个人觉得用async await的写法的话代码会看起来接近于同步,更加直观。
async/await是es7的语法,在小程序中直接写会报错。解决方法就是去facebook的generator库 下载一个runtime.js,在使用async/await语法的地方引入该js就可以正常使用了。

const regeneratorRuntime = require("../../lib/runtime.js");

抽象请求逻辑

在页面中经常调用云函数获取查询数据库的api会写出很多重复的代码,于是就再写了一个adapter.js封装这些请求,并做统一错误处理

const db = wx.cloud.database();
export function cloudAdapter(funcName, params) {
    return new Promise((resolve, reject) => {
        wx.cloud.callFunction({
            name: funcName,
            data: params || {},
            success: res => {
                resolve(res)
                console.log(`[云函数${funcName}] 调用成功: `, res);
            },
            fail: err => {
                resolve(null)
                console.log(`[云函数${funcName}] 调用失败: `, err);
            }
        })
    })
}

封装过后在页面上调云函数时代码就变成下面这样子了,比原来简洁了很多

const result = await cloudAdapter("fetchRecords", { params });
result && dosomething(result)

原本的代码

wx.cloud.callFunction({
  name: 'add',
  data: {
    a: 1,
    b: 2,
  },
  success(res) {
    console.log(res.result.sum) 
  },
  fail: console.error
})

图片的使用

在小程序中如果要使用本地图片只能使用Image标签,如果是css中要用到图片的话就只能写cdn的地址了。还好使用了云开发,有一个云储存的功能。

把图片放在图片管理中,点击详情会有一个下载的链接,这个链接可以用在css中。
由于使用了sass,就把所有可能会复用的icon的图片都放进了一个公共的scss文件中,写成变量给各页面使用。

// variable.scss
// 图片链接
$icon-next: 'https://xxx';
$icon-prev: 'https://xxx';

组件化

taro框架让我们可以几乎完全按照react的方式去写组件,因为我们可以很方便地把代码分割,拆出公共组件。但是由于小程序中无法支持高阶组件,所以需要用到高阶组件的地方我选择了使用render props去实现。

发送模板信息

小程序可以发送模板信息给用户,但是有一个前提,用户一定要先使用过这个小程序。因为发送模板消息的接口需要用到一个formid,而这个formid必须要用户在手机上点击了按钮才能拿到。
目前我们的做法是写一个公共组件,将页面中的各种元素传进去,返回一个包了很多层button的元素。这样的话用户点击页面元素就会触发button手机formid的事件,将用户的formid存进数据库里。
每个formid只能发一条信息,而且有效期为7天,所以我们的处理逻辑是发信息的时候先从数据库把该用户的formid拿出来,找到可用的formid。发完消息后把该用户所有已过期和失效的formid从数据库里删除。

滚动事件与rpx转换

遇到一个页面滚动到某个位置某个元素要吸顶的功能,需要监听到滚动事件。在小程序中监听滚动事件可以用onpagescroll事件或者scroll-view,但是这两种返回来滚动的值单位都是px,是物理像素。在不同的机型中,同一个元素滚动到顶部所滚的物理像素是不一样的,需要转化成为rpx。在小程序中页面的宽度规范定义为750rpx,所以转换公式为

prx = 750 / screenWidth * px

最后

以上即为写这个小程序时的思考过程以及所踩的各种坑,希望对大家有帮助。