Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2022年的微信小程序开发技巧 #233

Open
yanyue404 opened this issue Jun 29, 2022 · 0 comments
Open

2022年的微信小程序开发技巧 #233

yanyue404 opened this issue Jun 29, 2022 · 0 comments

Comments

@yanyue404
Copy link
Owner

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。

参考

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant