最近在做一个微信小程序的项目,遇到了不少坑,不得不说微信小程序的官方文档写的真的不怎么样,一开始技术选型的时候也有考虑过第三方框架,mpvue,uniapp等,但是最后考虑了下,还是先了解一下原生小程序比较好,我始终认为一开始就上第三方框架并不是很好。
下面我总结了一些坑点和开发过程中容易遇到的问题。
wxss or less
对于写惯了less的开发来书,写wxss实在是效率低下,太煎熬了,对此可以使用vscode插件 easy-less 来解决,我是用vscode来写代码,微信开发工具只作预览。
网络请求
wx.request是小程序的网络请求方法,默认情况下,我们希望success是在服务器响应的状态码在2xx的时候触发,实际上不是,只要网络请求成功发出了,success方法就会触发,比如500状态码也会触发success,所以fail也只在网络请求没有发出的情况下触发,所以你必须在success重复写判断逻辑,而且也不能用promise,所以有必要封装一下
// 封装前
wx.request({
url: 'test.php', //仅为示例,并非真实的接口地址
data: {
x: '',
y: ''
},
header: {
'content-type': 'application/json' // 默认值
},
success (res) {
// 假设服务器的响应体是{success: true, data: 123}
if (res.statusCode === 2xx) {
fn(res.data.data)
} else {
wx.showToast({ icon: 'none', title: res.data.message || '网络异常', duration: 1500 })
}
},
fail (res) {
console.log(res);
}
})
// 封装后
// request.js
module.exports = function ({url, data, method}) { const app = getApp(); return new Promise((resolve, reject) => { wx.request({ method: method, url: `${app.globalData.apiUrl}/${url}`, data: data, header: { token: app.globalData.token }, success: function (res) { if (res.statusCode === 200) { const data = res.data.data; resolve(data); } else if (res.statusCode === 401) {
// 登录失效,包含业务逻辑,根据需求添加
wx.setStorageSync('token', null); // 清除token app.globalData.token = null; wx.setStorageSync('userInfo', {}); // 清除用户数据 app.globalData.userInfo = {}; wx.switchTab({ url: '/pages/user/user' }); reject(res.data); } else if (res.statusCode >= 500) { wx.showToast({ icon: 'none', title: res.data.message || '网络异常', duration: 1500 }); reject(res.data); } }, fail: function () { wx.showToast({ icon: 'none', title: '网络异常', duration: 1500 }); } }); })}
// app.js
const request = require('./utils/request.js');App({ onLaunch: function () { this.request = request; }, globalData: { token: wx.getStorageSync('token'), userInfo: wx.getStorageSync('userInfo'), apiUrl: 'http://localhost:3300/api/' }})// index.js
const app = getApp();app.request({
method: 'GET', url: '/test'}).then(res => { // do something});复制代码
Promise finally
因为在某些接口请求前会加loading,防止多次点击,然后在接口的finally中取消loading即可,在开发工具中,一切正常,但是一到真机调试就会报错,查阅资料发现微信小程序不支持promise finally,太坑了,只能加个 polyfill
canvas绘图
小程序一般都会有分享图片的需求,图片一般都带用户信息和小程序码,这时候就要用到canvas,而原生的canvas是如此难用和坑,所以想使用三方库,一开始试了html2canvas,
html2canvas
它确实很强大,可以直接获取dom绘制,但是微信小程序无法获取dom,引入之后直接报错,thirdScriptError Cannot read property 'document' of undefined TypeError: Cannot read property 'document' of undefined
html2canvas(document.body).then(function(canvas) {
document.body.appendChild(canvas);
});复制代码
Painter
painter可以使用json数据描述绘图,不用繁琐操作canvas的一大坨函数,方便很多,基本能满足需求了。中间遇到一个小问题,开发工具里,绘图后保存到相册是有头像的,但是发小程序正式包后,真机操作时,绘图的头像就会丢失,一开始以为是painter的兼容问题,后来才发现是因为开发工具是开启了不校验合法域名的,但是线上包会校验合法域名,天真的以为微信自己的头像域名不用添加,结果并不是,微信连自己都不放过,在微信小程序后台添加下合法域名就好了 https://wx.qlogo.cn
获取小程序码
微信有3中方式获取小程序码
wxacode.createQRCode
获取小程序二维码,适用于需要的码数量较少的业务场景。通过该接口生成的小程序码,永久有效,有数量限制
wxacode.get
获取小程序码,适用于需要的码数量较少的业务场景。通过该接口生成的小程序码,永久有效,有数量限制
wxacode.getUnlimited
获取小程序码,适用于需要的码数量极多的业务场景。通过该接口生成的小程序码,永久有效,数量暂无限制
好像没有什么理由不选第三种吧
这3个接口返回的都是图片Buffer,我们需要做下处理,有2种方式
方式1:把图片存到服务器本地或转存到alioss等第三方对象服务器上,最后把地址返回给小程序前端
// 服务器
let readable;
let filePath = xxx; // 自己创建一个存图片的目录
const url = `https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=${access_token}`;const result = await axios.post(url, { scene: `a=${xxx}` // 小程序码带的参数,比如带一个邀请码 }, { headers: { 'Content-Type': 'application/json' // POST 参数需要转成 JSON 字符串,不支持 form 表单提交。 }, responseType: 'arraybuffer' }); readable = result.data; readable.pipe(fs.createWriteStream(filePath));复制代码
方式2:把Buffer转成base64返回给小程序前端,前端在转成图片存到小程序本地
// 服务器
const url = `https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=${access_token}`; const result = await axios.post(url, { scene: `a=${xxx}` // 小程序码带的参数,比如带一个邀请码 }, { headers: { 'Content-Type': 'application/json' // POST 参数需要转成 JSON 字符串,不支持 form 表单提交。 }, responseType: 'arraybuffer' }); const base64 = Buffer.from(result.data).toString('base64');
// 返回前端 res.send({ success: true, data: `data:image/jpg;base64,${base64}` });
// 小程序前端
// base64src.js base64转图片存到小程序临时目录中
const fsm = wx.getFileSystemManager();const FILE_BASE_NAME = 'qrcode_base64src';const base64src = function(base64data) { return new Promise((resolve, reject) => { const [, format, bodyData] = /data:image/(w+);base64,(.*)/.exec(base64data) || []; if (!format) { reject(new Error('ERROR_BASE64SRC_PARSE')); } const filePath = `${wx.env.USER_DATA_PATH}/${FILE_BASE_NAME}.${format}`; const buffer = wx.base64ToArrayBuffer(bodyData); fsm.writeFile({ filePath, data: buffer, encoding: 'binary', success() { resolve(filePath); }, fail() { reject(new Error('ERROR_BASE64SRC_WRITE')); }, }); });};module.exports = base64src;// index.js
let base64Data = xxx; // 服务器返回的base64数据base64src(base64Data).then((src) => { wx.getImageInfo({ src: src, success: function (r) { console.log(r.path); // 图片本地路径 }, fail: function (r) { console.log(r); } }); });
复制代码
自定义组件 behavior
有时候某些自定义组件会有大部分的相似功能,小部分差异,这时候可以使用behavior封装共同的属性和方法等,类似vue中的mixins。