- 【优化】利用函数防抖和函数节流提高小程序性能
大家好,上次给大家分享了swiper仿tab的小技巧: https://developers.weixin.qq.com/community/develop/doc/000cc0b94ac5f8dcf4e7666475b804 今天给大家分享两个有用的函数,《函数防抖和函数节流》 函数防抖和函数节流是都优化高频率执行js代码的一种手段,因为是js实现的,所以在小程序里也是适用的。 首先先来理解一下两者的概念和区别: 函数防抖(debounce)是指事件在一定时间内事件只执行一次,如果在这段时间又触发了事件,则重新开始计时,打个很简单的比喻,比如在打王者荣耀时,一定要连续干掉五个人才能触发hetai kill '五连绝世'效果,如果中途被打断就得重新开始连续干五个人了。 函数节流(throttle)是指限制某段时间内事件只能执行一次,比如说我要求自己一天只能打一局王者荣耀。 这里也有个可视化工具可以让大家看一下三者的区别,分别是正常情况下,用了函数防抖和函数节流的情况下:http://demo.nimius.net/debounce_throttle/ 适用场景: 函数防抖 搜索框搜索联想。只需用户最后一次输入完,再发送请求 手机号、邮箱验证输入检测 窗口resize。只需窗口调整完成后,计算窗口大小。防止重复渲染 高频点击提交,表单重复提交 函数节流 滚动加载,加载更多或滚到底部监听 搜索联想功能 实现原理 函数防抖 [代码]const _.debounce = (func, wait) => {[代码][代码] [代码][代码]let timer;[代码] [代码] [代码][代码]return[代码] [代码]() => {[代码][代码] [代码][代码]clearTimeout(timer);[代码][代码] [代码][代码]timer = setTimeout(func, wait);[代码][代码] [代码][代码]};[代码][代码]};[代码] 函数节流 [代码]const throttle = (func, wait) => {[代码][代码] [代码][代码]let last = 0;[代码][代码] [代码][代码]return[代码] [代码]() => {[代码][代码] [代码][代码]const current_time = +[代码][代码]new[代码] [代码]Date();[代码][代码] [代码][代码]if[代码] [代码](current_time - last > wait) {[代码][代码] [代码][代码]func.apply([代码][代码]this[代码][代码], arguments);[代码][代码] [代码][代码]last = +[代码][代码]new[代码] [代码]Date();[代码][代码] [代码][代码]}[代码][代码] [代码][代码]};[代码][代码]};[代码] 上面两个方法都是比较常见的,算是简化版的函数 lodash中的 Debounce 、Throttle lodash中已经帮我们封装好了这两个函数了,我们可以把它引入到小程序项目了,不用全部引入,只需要引入debounce.js和throttle.js就行了,链接:https://github.com/lodash/lodash 使用方法可以看这个代码片段,具体的用法可以看上面github的文档,有很详细的介绍:https://developers.weixin.qq.com/s/vjutZpmL7A51 系甘先,得闲饮茶
2019-01-14 - 小白的小程序二维码canvas绘制之旅--node.js
要做一个分享到朋友圈的功能,但是微信没有相关的接口,只能通过将相关页面生成小程序码,然后生成图片,让用户保存到相册,再分享到朋友圈。于是,经过三天的折腾终于搞出来了,虽然不知道是不是正确的方法,还是分享出来,给同样没有经验的童鞋作参考。 首先是遇到的一些坑: 1. 开始的时候直接在前端调用接口,但是发现 api.weixin.qq.com 添加到请求域名,因此只能从后端获取。 2. 后端的请求我不会,T^T,这个确实是我自己太菜了,我用的 node.js,搜索发现使用 var request = require('request') 就可以了。 3. 后端请求的时候,因为这是异步操作,导致微信接口还没有返回结果,我这边接口就已经返回小程序端了,这里需要 promise 搭配 await 来实现进程阻塞。 4. 获取到token之后,使用生成二维码的接口 getwxacodeunlimit,得到返回值是一堆乱码,我以为这是正常的数据,于是开始想办法处理这些数据。网上搜到的是用 pipe 和 fs 存到服务器,在从前端把图片从服务器下载下来,在canvas画,但是我用的腾讯云小程序方案,根本不知道图片存到了哪里,也不知道下载链接。所以我就尝试将乱码发送到前端,用 arrayBufferToBase64 处理,发现根本没有输出。。。最后折腾了好久,发现是最开始获取的时候,接受数据的编码格式不对,在 request 的请求参数 opts 里添加一句 :encoding: null ,就可以正确接受,结果是一个buffer变量,里面是多维数字数组。 5. 得到数据之后,我以为只要能在前端显示,就能在canvas里画了,于是就把buffer发送到前端,然后用arrayBufferToBase64 处理,再加上 "data:image/png;base64," 头,就可以作为 image 标签的 src 参数进行显示了,然而,这种 base64 图片,在开发工具上可以画在 canvas里,真机上却不行。官方不支持。。。。所以饶了一圈,又得回到原点。最后还是通过在后端,将得到的buffer上传到cos对象存储,再将存储地址返回到前端,前端通过wx.downloadFile将图片下载下来,再画到 canvas 上。 然后是一些要注意的点: 1. 真机获取二维码只能通过后端实现。 2. canvas画图是异步操作,需要延时后执行 canvasToTempFilePath 3. 除了B接口,另外两个是有个数限制的,要小心调用,用完就没了。 4. token调用也是有次数限制的,需要做处理。 5. canvas大小需要自己手动根据屏幕尺寸进行调整。 6. 我用的wafer 2 ,node.js后端,cos存储 7. 生成的二维码进入的是已经发布的版本,我的是已经有一个发布的版本的。 最后是代码(水平有限): 后端,放在server/controllers/getqrcode.js: [代码]const { mysql } = require([代码][代码]'../qcloud'[代码][代码])[代码][代码]//腾讯云对象存储接口[代码][代码]const COS = require([代码][代码]'cos-nodejs-sdk-v5'[代码][代码])[代码][代码]// 获取基础配置[代码][代码]const configs = require([代码][代码]'../config'[代码][代码])[代码][代码]//请求包[代码][代码]var[代码] [代码]request = require([代码][代码]'request'[代码][代码])[代码] [代码]// token有效时间[代码][代码]var[代码] [代码]period = 7200000; [代码][代码]//小程序开发信息[代码][代码]const APPID = [代码][代码]'wxxxxxxxxxxxxxxxxxx'[代码][代码]const APPSECRET = [代码][代码]'b3dxxxxxxxxxxxxxxxxxxxxxx'[代码] [代码]module.exports = async ctx => {[代码][代码] [代码][代码]//小程序传入的参数,path是页面路径放到page里,pptid是参数,放到scene里的[代码][代码] [代码][代码]const pptid = ctx.query.pptid[代码][代码] [代码][代码]const path = ctx.query.path[代码][代码] [代码][代码]let result = [代码][代码]''[代码][代码] [代码][代码]let msg = [代码][代码]'init'[代码][代码] [代码][代码]let id = 1[代码][代码] [代码][代码]let token =[代码][代码]'none'[代码][代码] [代码][代码]var[代码] [代码]urlpath = [代码][代码]'none'[代码][代码] [代码][代码]console.log([代码][代码]'getqrcode\r\n'[代码][代码])[代码][代码] [代码][代码]const getAccessToken = [代码][代码]function[代码] [代码]() {[代码][代码] [代码][代码]//获取token[代码][代码] [代码][代码]var[代码] [代码]opts = {[代码][代码] [代码][代码]method: [代码][代码]'GET'[代码][代码],[代码][代码] [代码][代码]url: [代码][代码]'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid='[代码] [代码]+ APPID + [代码][代码]'&secret='[代码] [代码]+ APPSECRET,[代码][代码] [代码][代码]header: {[代码][代码] [代码][代码]'content-type'[代码][代码]: [代码][代码]'application/json'[代码] [代码]// 默认值[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}[代码][代码] [代码][代码]console.log([代码][代码]'do get AccessToken!\r\n'[代码][代码])[代码][代码] [代码][代码]return[代码] [代码]new[代码] [代码]Promise((resolve, reject) => {[代码][代码] [代码][代码]request.get(opts, [代码][代码]function[代码] [代码](err, response, body) {[代码][代码] [代码][代码]if[代码] [代码](!err && response.statusCode == 200) {[代码][代码] [代码][代码]if[代码] [代码](body !== [代码][代码]'null'[代码][代码]) {[代码][代码] [代码][代码]results = body;[代码][代码] [代码][代码]code = JSON.parse(results);[代码][代码] [代码][代码]if[代码] [代码](code.access_token != [代码][代码]null[代码][代码]) {[代码][代码] [代码][代码]let accessToken = code.access_token[代码][代码] [代码][代码]resolve(accessToken)[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]msg = [代码][代码]'joberror'[代码][代码] [代码][代码]console.log([代码][代码]'job error:'[代码] [代码]+ results);[代码][代码] [代码][代码]reject(results)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]msg = [代码][代码]'getfail'[代码][代码] [代码][代码]reject(msg)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]msg = [代码][代码]'netfail'[代码][代码] [代码][代码]reject(err)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]});[代码][代码] [代码][代码]})[代码][代码] [代码][代码]};[代码][代码] [代码][代码]const getqrc = [代码][代码]function[代码] [代码](token) {[代码][代码] [代码][代码]//请求二维码的参数 [代码][代码] [代码][代码]var[代码] [代码]postData = {[代码][代码] [代码][代码]page: path,[代码][代码] [代码][代码]width: 430,[代码][代码] [代码][代码]scene: pptid[代码][代码] [代码][代码]} [代码][代码] [代码][代码]var[代码] [代码]opts = {[代码][代码] [代码][代码]method: [代码][代码]'POST'[代码][代码],[代码][代码] [代码][代码]url: [代码][代码]'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token='[代码] [代码]+ token,[代码][代码] [代码][代码]body: JSON.stringify(postData),[代码][代码] [代码][代码]encoding:[代码][代码]null[代码][代码],[代码][代码] [代码][代码]// responseType:"arraybuffer",//这个好像没用[代码][代码] [代码][代码]}[代码][代码] [代码][代码]console.log([代码][代码]'do get QRcode!\r\n'[代码][代码])[代码][代码] [代码][代码]return[代码] [代码]new[代码] [代码]Promise((resolve, reject) => { [代码][代码] [代码][代码]request(opts, [代码][代码]function[代码] [代码](err, response, body) {[代码][代码] [代码][代码]if[代码] [代码](!err && response.statusCode == 200) {[代码][代码] [代码][代码]if[代码] [代码](body !== [代码][代码]'null'[代码][代码]) {[代码][代码] [代码][代码]result = body[代码][代码] [代码][代码]// console.log(body)[代码][代码] [代码][代码]msg = [代码][代码]'qrdone'[代码][代码] [代码][代码]resolve(body)[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]msg = [代码][代码]'qrgetfail'[代码][代码] [代码][代码]reject(msg)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]msg = [代码][代码]'qrnetfail'[代码][代码] [代码][代码]reject(err)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]})[代码][代码]//.pipe(fs.createWriteStream('./temp2code.jpg'))[代码] [代码] [代码][代码]})[代码][代码] [代码][代码]};[代码][代码] [代码][代码]const uploadimg=[代码][代码]function[代码][代码](body){[代码][代码] [代码][代码]//上传到cos[代码][代码] [代码][代码]//body就是上一步获取到的body[代码][代码] [代码][代码]//存到cos后自动就生成图片了 [代码][代码] [代码][代码]var[代码] [代码]cos = [代码][代码]new[代码] [代码]COS({[代码][代码] [代码][代码]// 必选参数[代码][代码] [代码][代码]SecretId: configs.cos.SecretId,[代码][代码] [代码][代码]SecretKey: configs.cos.SecretKey,[代码][代码] [代码][代码]// 可选参数[代码][代码] [代码][代码]FileParallelLimit: 3, [代码][代码]// 控制文件上传并发数[代码][代码] [代码][代码]ChunkParallelLimit: 8, [代码][代码]// 控制单个文件下分片上传并发数,在同园区上传可以设置较大的并发数[代码][代码] [代码][代码]ChunkSize: 1024 * 1024, [代码][代码]// 控制分片大小,单位 B,在同园区上传可以设置较大的分片大小[代码][代码] [代码][代码]});[代码][代码] [代码][代码]return[代码] [代码]new[代码] [代码]Promise((resolve, reject) => { [代码][代码] [代码][代码]cos.putObject({[代码][代码] [代码][代码]Bucket: configs.cos.Bucket, [代码][代码]// Bucket 格式:test-1250000000[代码][代码] [代码][代码]Region: configs.cos.region,[代码][代码] [代码][代码]Key: [代码][代码]'qrcode-'[代码][代码]+pptid+[代码][代码]'.jpg'[代码][代码], [代码][代码]/* 给文件起个名字 */[代码][代码] [代码][代码]Body: body, [代码][代码]/* 必须 */[代码][代码] [代码][代码]}, [代码][代码]function[代码] [代码](err, data) {[代码][代码] [代码][代码]if[代码] [代码](err) {[代码][代码] [代码][代码]reject(err)[代码][代码] [代码][代码]console.log(err);[代码][代码] [代码][代码]} [代码][代码]else[代码] [代码]{[代码][代码] [代码][代码]resolve(data)[代码][代码] [代码][代码]// console.log(data);[代码][代码] [代码][代码]}[代码][代码] [代码][代码]});[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码][代码] [代码][代码]//查询数据库中的token是否需要更新[代码][代码] [代码][代码]let res = await mysql([代码][代码]"systemSet"[代码][代码]).where({ id }).first()[代码][代码] [代码][代码]console.log([代码][代码]'sql updated on:'[代码] [代码]+ res.updatetime + [代码][代码]'\r\n'[代码][代码])[代码][代码] [代码][代码]var[代码] [代码]timestamp = Date.parse([代码][代码]new[代码] [代码]Date());[代码][代码] [代码][代码]if[代码] [代码](timestamp - Date.parse(res.updatetime) < period){[代码][代码] [代码][代码]//不需要更新[代码][代码] [代码][代码]msg=[代码][代码]'good'[代码][代码] [代码][代码]token= res.accesstoken[代码][代码] [代码][代码]}[代码][代码]else[代码][代码]{[代码][代码] [代码][代码]//token太久了,需要更新[代码][代码] [代码][代码]try[代码] [代码]{[代码][代码] [代码][代码]const t = await getAccessToken()[代码][代码] [代码][代码]token = t[代码][代码] [代码][代码]//更新数据库中的token[代码][代码] [代码][代码]await mysql([代码][代码]"systemSet"[代码][代码]).update({[代码][代码] [代码][代码]accesstoken: token[代码][代码] [代码][代码]}).where({ id })[代码][代码] [代码][代码]msg = [代码][代码]'update'[代码][代码] [代码][代码]console.log(t.substring(0, 5) + [代码][代码]'...done!\r\n'[代码][代码])[代码][代码] [代码][代码]} [代码][代码]catch[代码] [代码](err) {[代码][代码] [代码][代码]console.log(err);[代码][代码] [代码][代码]}[代码][代码] [代码][代码]} [代码][代码] [代码][代码]//获取二维码数据[代码][代码] [代码][代码]if[代码] [代码](msg == [代码][代码]'good'[代码] [代码]|| msg == [代码][代码]'update'[代码][代码]) {[代码][代码] [代码][代码]try[代码] [代码]{[代码][代码] [代码][代码]var[代码] [代码]q = await getqrc(token)[代码][代码] [代码][代码]// console.log(q)[代码][代码] [代码][代码]try[代码] [代码]{[代码][代码] [代码][代码]// 存储到cos,并返回地址[代码][代码] [代码][代码]var[代码] [代码]p = await uploadimg(q)[代码][代码] [代码][代码]urlpath = [代码][代码]'https://'[代码] [代码]+ p.Location[代码][代码] [代码][代码]// console.log(p)[代码][代码] [代码][代码]} [代码][代码]catch[代码] [代码](err) {[代码][代码] [代码][代码]console.log(err)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]} [代码][代码]catch[代码] [代码](err) {[代码][代码] [代码][代码]var[代码] [代码]q = err[代码][代码] [代码][代码]console.log(err);[代码][代码] [代码][代码]}[代码][代码] [代码][代码]}[代码][代码]else[代码][代码]{[代码][代码] [代码][代码]var[代码] [代码]q = [代码][代码]'notoken'[代码][代码] [代码][代码]}[代码][代码] [代码][代码]//将地址返回到前端[代码][代码] [代码][代码]ctx.state.data = urlpath[代码][代码] [代码][代码]console.log([代码][代码]'finish\r\n'[代码][代码])[代码][代码]}[代码]小程序端代码(只贴获取二维码图片了): [代码]getqrcode:[代码][代码]function[代码][代码](){[代码][代码] [代码][代码]let that = [代码][代码]this[代码][代码] [代码][代码]wx.showLoading({[代码][代码] [代码][代码]title: [代码][代码]'图片正在生成中...'[代码][代码] [代码][代码]})[代码][代码] [代码][代码]qcloud.request({[代码][代码] [代码][代码]url: `${config.service.host}/weapp/getqrcode`,[代码][代码] [代码][代码]login: [代码][代码]false[代码][代码],[代码][代码] [代码][代码]data: { [代码][代码] [代码][代码]pptid: that.data.pptid,[代码][代码] [代码][代码]path:[代码][代码]'pages/detail/detail'[代码][代码] [代码][代码]},[代码][代码] [代码][代码]success(result) {[代码][代码] [代码][代码]//成功获取到了地址[代码][代码] [代码][代码]console.log([代码][代码]'Success get qrcode:'[代码][代码],result)[代码][代码] [代码][代码]let res = result.data.data[代码][代码] [代码][代码]// var base64 = wx.arrayBufferToBase64(res.data); [代码][代码] [代码][代码]// console.log(res)[代码][代码] [代码][代码]// that.setData({ imgurlqr: "data:image/png;base64," + base64 })[代码][代码] [代码][代码]//从cos下载图片[代码][代码] [代码][代码]wx.downloadFile({[代码][代码] [代码][代码]url: res,[代码][代码] [代码][代码]success: [代码][代码]function[代码] [代码](res0) {[代码][代码] [代码][代码]//获取到临时地址,进行canvas的绘制[代码][代码] [代码][代码]that.drawCanv(res0.tempFilePath)[代码][代码] [代码][代码]},[代码][代码] [代码][代码]fail: [代码][代码]function[代码] [代码](err) {[代码][代码] [代码][代码]console.log(err)[代码][代码] [代码][代码]}[代码][代码] [代码][代码]})[代码][代码] [代码][代码]},[代码][代码] [代码][代码]fail(error) {[代码][代码] [代码][代码]util.showModel([代码][代码]'请求失败'[代码][代码], error);[代码][代码] [代码][代码]console.log([代码][代码]'request fail'[代码][代码], error);[代码][代码] [代码][代码]}[代码][代码] [代码][代码]})[代码][代码] [代码][代码]},[代码]基本就这些了吧。心累。先这样用用看吧,不知道会不会有其他问题=^=
2018-09-22 - 《王者荣耀周边商城》经验总结
大家好,以下是《王者荣耀周边商城》小程序的一些经验总结,也许能帮到你,也许也帮不到,大家看着办哈,因为时间有点早,所以文中有些实现不是最新的,最终请以官方文档说明为准,废话不多说,直接上高清无码大图。 [图片] [图片] 以上截图是之前的版本,中间做了改版,大家可以直接扫码识别体验: [图片] 项目结构 我们都知道,小程序有自己的一套实现规范,下面我们看下小程序的项目结构,如下图: [图片] 一个入口文件:app.js 一个全局样式:app.wxss 一个全局配置:app.json 页面:pages下,每个页面再按文件夹划分,每个页面4个文件 index.js:实现页面整个生命周期的控制逻辑,置顶显示时的界面交互 index.json:页面配置,一个JSON对象,详细可配置字段见这里 index.wxml:UI结构渲染,可以理解为就是html,主要支持定制标签,更多标签见这里 index.wxss:UI样式渲染,可以理解为就是css,大部分css写法都支持。 当然在4个文件基础之下,还有一些通用的功能组件支撑它们的运行。每一个页面都是这么几个文件组成,非常规范统一,并且每一部分也都限定了内部实现框架和规范,所以在多人协作的时候,产出相对也就比较规范。 注:pages里面还可以再根据模块划分子目录,孙子目录,只需要在app.json里注册时填写路径就行 以上是必须的文件和目录,而实际中我们会增加别的目录,如lib,comm和utils等等目录。 框架设计 Web开发做得多了,你就会发现,大部分工作就是取数据,渲染UI,处理交互这三件事儿,小程序也不例外,所以按照这个框架逻辑,我们基于小程序本身的框架规范又扩展和细化了下,设计了下面的这套可直接应用于项目的开发框架。 [图片] 下面我们继续详细理一下设计时的一些思路、出发点和具体的实现方式,窥其面更要知其理。 注:这里我没有直接使用第三方的框架,因为我个人觉得要学习一套技术,还是需要从原生的模式开始着手,虽然前期会比较痛苦一点,但是这些付出都是值得的,因为你一旦搞明白了本来的逻辑架构和原理,你会发现什么框架都是信手拈来,而且你也更能理解框架这样设计的优点和缺点。 工具类库 Ajax 实现了promise的封装,支持GET POST PUT和DELETE,这里设计的时候就确定为仅满足单项目通用即可,所以实现的时候融入了部分业务层面的逻辑: 接口首次格式化,兼容标准的json和var形式接口(内部有大量var形式的接口) 直接判断返回值在逻辑上是成功还是失败 针对返回未登录的情况,自动跳转登录流程 所以省去了业务调用侧的反复判断处理通用逻辑,使用更简洁。 cache 其实,小程序自带了缓存接口,有同步wx.setStorageSync,异步wx.setStorage的方法,但是实际在使用缓存的场景里,我们一般都是需要设置缓存有效时间的,本cache工具就是对小程序缓存接口的封装,实现了对缓存有效期的支持。 Model实现 model层就不用多说了,主要是把数据处理部分独立出来,便于统一服务和维护,这里重点强调下model内部的实现细节,这里有一个实现技巧可以用在其它别的地方。下面直接上代码部分。 [图片] 上面的代码我们可以看到几个关键点 1. 把参数处理和返回结果处理拆出来放到单独的处理方法里,方法名称保持统一:formatParams,formatResult 2. 同时最外层定义好默认的formatParams,formatResult,如果不做特殊处理,直接使用默认即可(建议不处理也调用下默认方法,规范流程) 3. 还有一点,model里方法命令有统一规范都是已get,add,update,del开头 这个思路其实可以运用到任何场景,特别是在没有任何限定框架的场景,我们只需要按照这个模式去实现,代码一样很清晰漂亮,比如我后面实现LOL内置竞技场道具商店的时候,就是为了减少不必要的框架冗余代码,就直接徒手写的,同样是拆分为model和view层,然后model按上面的规范实现,代码同样很清晰,强烈建议大家实践下,简单实用。 组件模式 在实现王者周边小程序的时候,官方没有开放自定义组件规范,所以我们还不能按照内置组件的实现方式来实现我们业务侧自定义的组件,但是项目里又有公共组件的需求存在,那我们不管怎样还是需要把组件独立出来,不然重复代码很蛋疼,维护成本也比较高,实际我们这里的组件实现模式还是比较简单粗暴的,我们照样把组件拆分为JS,WXML,WXSS三部分(或者只有一部分也行),然后通过不同的import方式引入到需要使用组件的page里就行。 注:虽然这里没有使用官方的自定义组件规范,但是经过自己实现这个,也能大致了解到官方的自定义组件的实现方式和原理。 JS引入:import 或者 require(建议小程序这里引入都用import,跟wxml和wxss比较统一,我对代码有点小洁癖) WXML引入:和标签 WXSS引入:@import 组件很多时候也需要处理页面交互,相应事件,而小程序的事件绑定机制决定,事件处理方法必须是挂载到当前page对象下(实际是Page()方法定义的对象,内部引用是this),而组件的实现是单独的文件,不在Page()方法里定义,那怎么办呢?我这边的实现方式是组件初始化的地方,传递当前的page对象(this)给到组件,然后组件内部的接口方法全部一次性extend到page上,同样数据也是这个道理,WXML里面的数据方法只能是data对象,组件里的数据也需要挂载到这个对象上,这里强烈建议把组件内部的数据定义为一个单独的对象挂载到data上,而不要直接挂载,如我们这里的购物车组件,实现就是下面这样: [图片] 上面setData的时候,定义了shoppingCart对象,在它里面再定义具体的购物车组件需要的数据变量,而下面Object.assign一句就是把组件的方法挂载到当前使用组件的page上面去。 另外还要注意,在WXML里插入组件模块的时候,template标签的data属性里的名称请使用上面setData的名称,比如购物车这里就是shoppingCart。 请大家现在实现的时候,使用官方标准的自定义组件规范 数据共享 小程序开发也涉及到多页面间数据共享,这里针对不同的场景有几种实现方式: 1、基于页面的数据传递:直接在navigate的url后面增加参数即可,然后在接收的页面onLoad方法里,通过参数(对象)接收即可,如下: [图片] [图片] 2、基于内存的数据共享:getApp方法,获取全局的App实例对象,可以设置存取这个实例对象属性来实现数据共享,如下: [图片] 上面这种方式,适合与启动后的短期数据共享,关闭小程序数据会丢失。 3、基于本地缓存的数据共享:可以使用上面的cache组件,也可以使用原生的缓存接口实现,这种方式是可以在小程序关闭后还存在。 4、基于后台服务的接口缓存:这个不多说,就是保持数据到服务器,多页面通过接口调用。 开发规范 详细的JS实现规范这就不讲了,这里大致列一下我们在开发的时候,我们这边简单定义的一些规范,供大家参考。 JS模块引入请使用import关键字,而非require,WXML引入模板用标签,WXSS使用@import语法,三者统一 使用 let 代替var进行变量定义,使用const定义常量,如:let goods = 1,const SEX=’男’ 所有方法和变量名称都使用小写camel模式,一般是动词+名词形式,尽量不要超过5个单词,如:getList,setBackImage 所有用到的常量的地方都使用全大写,下划线分隔的形式,如:EGG_CHE 使用this转换的地方,统一使用that,如:let that = this; 尽量使用箭头函数,可保留this指向 所有自定义方法(onLoad,onShow等系统方法除外)必须使用规范注释语法进行注释 在page和model里定义方法的时候,直接使用getList(){} 即可,中间可以不用加function关键字 小程序声明周期函数里(onLoad,onReady,onShow,onHide,onUnload等)不要直接写复杂业务逻辑,复杂业务逻辑独立成方法,这里只负责方法调用。 所有数据处理必须封装到model里面,包括url地址,参数格式化,返回结果格式化都放到model里面,在page里使用的时候,基本不用做过多数据处理,一般都直接setData皆可,model规范请参考前面的说明。 如果1个功能在超过一个地方出现,那请实现为公用组件,组件实现请参考前面的说明。 在WXML模板里,如果同时有2个循环中都使用到了同样的代码片段,请使用template的方式定义,然后直接使用即可,不能重复写. 所有地方都需要有对接口返回空数据的处理,界面上要有相应的提示和引导。 其它的可以定义规范的loading,成功失败提示等,这里不多说了. 经验分享 大家开发前,可以大致浏览下小程序的官方文档,相对比较完善,遇到问题可以先查文档,然后再去小程序社区里搜索相关帖子,一般问题都能解决,下面是我遇到的一些问题和相应的解决方案,也许你也会碰到,仅供参考。 1、setData相关经验 设置多级对象值:this.setData({‘a.b.c.d’:value}); 设置可变索引的数组值: varkey="array["+index+"].text, data ={key: 'changed data’}; this.setData(data); 2、swiper组件 current 问题 swiper组件切换数据源以后,current属性也需要手动重置,不会默认恢复到第一帧,就可能出现当前current大于新数据源的长度,显示会出问题 3、picker-view 初始值设置无效 picker-view初始化的时候,我们都会设置数据源和初始索引值,结果发现放到一次setData里既然不生效,分成2次setData就可以了,应该是设置数据源的时间点在初始值之后了,因为setData接收的是一个hash对象,而hash对象是没有先后顺序的,所以就可能存在初始值在数据源之前设置了,当然初始值不可能生效。 [图片] 4、android 兼容性问题 从原理上我们知道,小程序本身还是基于不同的JS容器的执行的,所以由于IOS(jscore)和android(v8)上容器不一致,还是需要小程序开发者自己处理两个平台的兼容性,不过新版的微信升级后,目前ios和android的不一致的兼容性问题已经很少了,不要这里需要理解的是小程序虽然有规范,但是并没有帮我们屏蔽底层的兼容问题,我们自己需要注意。 5、cookie 的问题 我们在开发web页面的时候,肯定会使用到cookie,传递登录信息等,但是小程序本身不支持cookie,所以需要应用到cookie的地方,可以转换为参数,放到请求后面,我们上面的小程序登录态就是放到了请求参数里。 但是在调用wx.request的时候,是可以设置cookie header头的,所以如果后台接口验证的需要cookie支持,可以直接在这里设置即可,但是需要注意:android的版本的小程序会把cookie键名自动改为小写,如果后台是通过大写读取的话,可能就取不到值了,暂时还不确认新版是否已修复这个问题。 [图片] 6、https 的问题 小程序要求所有请求接口都必须是https的,而且所有的域名都需要在小程序管理后台去添加,如果碰到没有添加的情况,开发调试阶段可以在小程序开发工具的项目一栏下,把下面这句勾选,不过上线之前是一定要添加的,不然会出现本地怎么调都是好的,到了手机上就是不行。 [图片] 7、关于支付 直接使用微信支付即可,不多讲。 8、关于设计 如果有条件,小程序需要独立的产品设计和规划,照搬App或者H5版本不是最好的方案,因为小程序有自己的一套设计,交互规范,有基于微信的账号体系,消息机制等基础能力,我们都可以充分利用。 写在最后 虽然版本有些老,但是还是希望对大家有些帮助,最后建议,大家如果在开发小程序的过程中遇到问题,可以第一时间在论坛里发帖求助哈,微信官方有开发同学直接回复大家。
2018-01-31 - 高适应性的自定义导航栏开发思路
[图片] 非自定义导航栏高度怎么计算? 自定义导航栏高度由谁决定? 小程序自定义导航栏开发注意点与参考文档 一、默认导航栏高度怎么计算?(非custom情况下获取) wx.getSystemInfo 和 wx.getSystemInfoSync 获取机器信息 screenHeight - windowHeight 计算标题栏高度 [代码]{[代码][代码] [代码][代码]'iPhone'[代码][代码]: 64,[代码][代码] [代码][代码]'iPhone X'[代码][代码]: 88,[代码][代码] [代码][代码]'android'[代码][代码]: 68[代码][代码] [代码][代码]}[代码]不完全统计(ip6 , ip5 , ip6p , ipx , 小米mix2 , 小米5等综合了开发工具提供的数据和真机数据)所得 二、自定义导航栏高度由谁决定?(自定义情况下,屏幕高度和窗口高度没有差别,所以要通过步骤1先获取数据,预定义到代码中) 开发时发现,自定义导航栏的实现需要 包含状态栏+胶囊 :没有自定义导航栏的时候页面是全屏幕滚动会出现在状态栏的下一层 根据上一标题中步骤1的函数,可以获得状态栏高度 statusBarHeight demo,点击打开小程序开发工具 三、小程序自定义导航栏开发注意点与参考文档 微信官方设计指导中关于胶囊按钮的描述 由此得知胶囊宽度87pt=116px,设置之后,的确能产生较好的兼容性效果 社区Q&A:自定义标题栏高度计算、在 navigationStyle: 'custom',苹果X和8兼容问题 注意某些方法、参数的兼容性,时刻关注官方更新信息 开一个项目采集设备的screenHeight,windowHeight,pixelRatio等信息到一个数据库中,或者微信可以提供这样一个数据库便于计算,亦或者微信优化自定义标题栏(譬如通知栏可以改变颜色但不要算在自定义范围内,给出胶囊宽高到通知栏距离到右侧屏幕边框距离等相关参数)
2018-07-31