blog
blog copied to clipboard
2022年的微信小程序开发技巧
1. 使用解构赋值、mixins 的方式对小程序 page.js 可复用的代码的组合,减少重复
// 用basePage复用的Page实现
Page({
...getBasePage(),
...{
// 这里就和正常的page一样实现就好
data:{
a:1
},
next(){
// 子页面只需要专注实现next函数即可
},
})
2. require 的路径不支持绝对路径
源码: const util = require('../../../utils/fetch.js')
解决:在 App 绑定 require,Page 里获取 app,直接 app.require 引入。
// in app.js
App({
onLaunch() {},
require(path) {
return require(`${path}`);
},
});
// in page.js
const app = getApp();
const util = app.require("./utils/util.js");
3. miniprogram-api-promise、wx-promise-pro 扩展小程序 api promise 化
// 例: wx-promise-pro
wx.pro
.showLoading({
title: "加载中",
mask: true,
})
.then(() => console.log("in promise ~"));
4. 动画
WxCountUp 数字滚动,代替 requestAnimationFrame 帧渲染。
import WxCountUp from "../../plugins/wx-countup/WxCountUp.js";
Page({
data: {
number: 0,
},
onLoad: function () {
// 最后一个参数必填
this.countUp = new WxCountUp("number", 5234, {}, this);
},
start() {
this.countUp = new WxCountUp("number", 5234, {}, this);
// 开始动画
this.countUp.start();
// this.countUp.start(() => console.log('Complete!'));
},
pauseResume() {
// 暂停/重新开始
this.countUp.pauseResume();
},
reset() {
// 重置
this.countUp.reset();
},
update() {
// 更新为新值
this.countUp.update(1000);
},
});
5. npm 包
合理使用小程序 npm 包的组件化方式复用组件等。
js 中引入 npm 包:
const myPackage = require("packageName");
const packageOther = require("packageName/other");
使用 npm 包中的自定义组件:
{
"usingComponents": {
"myPackage": "packageName",
"package-other": "packageName/other"
}
}
6. 性能优化
评测方法与规则,结合我的优化实例:
(1)存在渲染界面的耗时过长的情况;
渲染界面的耗时过长会让用户觉得卡顿,体验较差,出现这一情况时,需要校验下是否同时渲染的区域太大
解决方案:页面数据过大,一次渲染耗费时长,按模块分多次渲染,加入骨架屏
(2)存在 setData 的数据过大
由于小程序运行逻辑线程与渲染线程之上,setData 的调用会把数据从逻辑层传到渲染层,数据太大会增加通信时间;变量单次赋值 598k;
解决方案:产品列表页所有数据来源一个接口,数据过大,大量冗余字段;先是接口拆分,然后数据清洗,去除无用字段,减少数据大小,一般不超过 256k;
(3)滚动区域没有开启惯性滚动
惯性滚动会使滚动比较顺畅,在安卓下默认有惯性滚动,而在 iOS 下需要额外设置 -webkit-overflow-scrolling: touch 的样式;
解决方案:使用 scroll-view 组件,添加-webkit-overflow-scrolling: touch 的样式
(4)存在将未绑定在 WXML 的变量传入 setData
setData 操作会引起框架处理一些渲染界面相关的工作,一个未绑定的变量意味着与界面渲染无关,传入 setData 会造成不必要的性能消耗;
解决方案:按要求处理,减少 setData 的调用 (可以使用 this.data 存储不需要在 wxml 中展示的变量)
(5) 存在短时间内发起太多的图片请求
短时间内发起太多图片请求会触发浏览器并行加载的限制,可能导致图片加载慢,用户一直处理等待。应该合理控制数量,可考虑使用雪碧图技术、拆分域名或在屏幕外的图片使用懒加载
解决方案:懒加载需要监听滚动的高度,计算当前 dom 的高度,调用 setData 改变图片的显隐状态,会增加另外性能损失,再考虑...
(6)存在 setData 的调用过于频繁
setData 接口的调用涉及逻辑层与渲染层间的线程通过,通信过于频繁可能导致处理队列阻塞,界面渲染不及时而导致卡顿,应避免无用的频繁调用 pages/home/home:onPageScroll 方法 38 次/秒,touchEnd 方法 26 次/秒;
解决方案:滚动监听处理数据,使用节流处理;页面其他多次调用,减少非必要的调用,非数据绑定的使用常规赋值方法;
7. 错误监控
给小程序增加错误信息收集,包括 js 脚本错误信息收集和 http 请求错误信息收集。
脚本错误收集
对于脚本错误收集,这个相对比较简单,因为在 app.js 中提供了监听错误的 onError 函数。
只不过错误信息是包括堆栈等比较详细的错误信息,然后当上传时我们并不需要这么信息,第一浪费宽带,第二看着累又无用。我们需要的信息是:错误类型、错误信息描述、错误位置。
thirdScriptError
aa is not defined;at pages/index/index page test function
ReferenceError: aa is not defined
at e.test (http://127.0.0.1:62641/appservice/pages/index/index.js:17:3)
at e.<anonymous> (http://127.0.0.1:62641/appservice/__dev__/WAService.js:16:31500)
at e.a (http://127.0.0.1:62641/appservice/__dev__/WAService.js:16:26386)
at J (http://127.0.0.1:62641/appservice/__dev__/WAService.js:16:20800)
at Function.<anonymous> (http://127.0.0.1:62641/appservice/__dev__/WAService.js:16:22389)
at http://127.0.0.1:62641/appservice/__dev__/WAService.js:16:27889
at http://127.0.0.1:62641/appservice/__dev__/WAService.js:6:16777
at e.(anonymous function) (http://127.0.0.1:62641/appservice/__dev__/WAService.js:4:3403)
at e (http://127.0.0.1:62641/appservice/appservice?t=1543326089806:1080:20291)
at r.registerCallback.t (http://127.0.0.1:62641/appservice/appservice?t=1543326089806:1080:20476)
这是错误信息字符串,接下来我们对它进行截取只需要拿我们想要的信息即可。我们发现这个字符串是有规则的。第一行是错误类型,第二行是错误详情和发生的位置,并且是";"分好分开。所以我们还是很容易就可以拿到我们想要的信息。
//格式化错误信息
function formateErroMsg(errorMsg) {
//包一层try catch 不要让信息收集影响了业务
try {
let detailMsg = "";
let detailPosition = "";
let arr = errorMsg.split("\n");
if (arr.length > 1) {
//错误详情和错误位置在第二行并用分好隔开
const detailArr = arr[1].split(";");
detailMsg = detailArr.length > 0 ? detailArr[0] : "";
if (detailArr.length > 1) {
detailArr.shift();
detailPosition = detailArr.join(";");
}
}
const obj = {
//错误类型就是第一行
error_type: arr.length > 0 ? arr[0] : "",
error_msg: detailMsg,
error_position: detailPosition,
};
return obj;
} catch (e) {}
}
获取到我们想要的信息,就可以发送到我们服务后台,进行数据整理和显示,这个需要服务端配合,就不深入讲了,我们拿到了数据,其他都不是事。
http 请求错误信息收集
对于 http 请求错误信息收集方式,我们尽量不要暴力埋点,每个请求发送前发送后加上我们的埋点。这样工作量太大,也不易维护。因此,我们可以从底层出发,拦截 wx.request 请求。使用 Object.definePropert 对 wx 对象的 request 进行重新定义。具体实现如下:
// 请求排除:对于发送错误信息的接口不收集,防止死循环
const reqExclude = [/reciveFrontEndResourceToS3|appendVideo|healthcare/i];
function rewriteRequest() {
try {
const originRequest = wx.request;
Object.defineProperty(wx, "request", {
configurable: true,
enumerable: true,
writable: true,
value: function () {
let options = arguments[0] || {};
if (reqExclude.some((reg) => reg.test(options.url))) {
//这里要执行原来的方法
return originRequest.call(this, options);
}
//这里拦截请求成功或失败接口,拿到请求后的数据
["success", "fail"].forEach((methodName) => {
let defineMethod = options[methodName];
options[methodName] = function () {
try {
//在重新定义函数中执行原先的函数,不影响正常逻辑
defineMethod && defineMethod.apply(this, arguments);
//开始信息收集
let statusCode, result, msg;
//请求失败
if (methodName == "fail") {
statusCode = 0;
result = "fail";
msg = (arguments[0] && arguments[0].errMsg) || "";
}
//请求成功,
//收集规则为:
// 1、 statusCode非2xx,3xx
// 2、 statusCode是2xx,3xx,但接口返回result不为ok
if (methodName == "success") {
let data = arguments[0] || {};
statusCode = data.statusCode || "";
if (
data.statusCode &&
Number(data.statusCode) >= 200 &&
Number(data.statusCode) < 400
) {
let resData = data.data
? typeof data.data == "object"
? data.data
: JSON.parse(data.data)
: {};
//请求成功,不收集
if (resData.result == "ok") {
return;
}
result = resData.result || "";
msg = resData.msg || "";
} else {
result = "";
msg = data.data || "";
}
}
//过滤掉header中的敏感信息
if (options.header) {
options.header.userid && delete options.header.userid;
}
//过滤掉data中的敏感信息
if (options.data) {
options.data.userid && delete options.data.userid;
}
let collectInfo = {
url: options.url || "", //请求地址
method: options.method || "GET", //请求方法
request_header: JSON.stringify(options.header || {}), //请求头部信息
request_data: JSON.stringify(options.data || {}), //请求参数
resp_code: statusCode + "", //请求状态码
resp_result: result, //请求返回结果
resp_msg: msg, //请求返回描述信息
};
//提交参数与上一次不同,或者参数相同,隔了1s
if (
JSON.stringify(collectInfo) != lastParams.paramStr ||
new Date().getTime() - lastParams.timestamp > 1000
) {
//上传错误信息
Post.post_error(_miniapp, "http", collectInfo);
lastParams.paramStr = JSON.stringify(collectInfo);
lastParams.timestamp = new Date().getTime();
}
} catch (e) {
//console.log(e);
}
};
});
return originRequest.call(this, options);
},
});
} catch (e) {
// Do something when catch error
}
}
包装拦截 wx.request 如下:
function my_request() {
//只要执行一次拦截代码即可
!_isInit && rewriteRequest();
return wx.request(options);
}
接下来我们看下后台数据,持续监控,会帮我们找出很多隐藏的 bug。