大家好,我是HoMeTown,最近要做一个小程序的项目,项目启动之前,回顾自己之前做过的小程序,感觉做的还是不够好,最近学习了一下小程序优化方案,这块总结一份个人笔记,以便参考,同时分享给大家,共勉。
微信小程序上线至今已经大约已经有5年
的时间,5年的时间,小程序的开发能力经过微信团队的不断努力优化,已经日益完善,不再像之前一样,翻翻文档就能完成一个简易的项目了,特别是性能优化方面,会优化与不会优化更是对产品的效果有着决定性的影响。
有了优化的方向,就可以根据问题逐一诊断了
App.onLaunch
还有App.onShow
这些事件」initDataSendTime
,会派发Page.onLoad
事件viewLayerReaderStartTime
,会派发Page.onShow
事件viewLayerReaderEndTime
,会派发Page.onReady
事件,意味着首屏渲染完成冷启动
小程序在用户设备上第一次打开或者是销毁之后再打开,或者是30分钟以后
热启动
热启动是相对于冷启动而言的,热启动是小程序启动的一种优化机制,小程序进入后台30分钟以内再次进到前台,可以直接从后台状态然后回复到前台,所以,在这种情况下,刚刚那个代码注入
、首屏渲染
等基础工作就不会再执行了,设置App.onLaunch
、Page.onLoad
等这些一次性的生命周期,也不会有了。
结论
由此,可以得出,要做小程序的性能优化,主要是做冷启动
这块的优化以及运行时渲染性能的一个优化,小程序冷启动流程里涉及到一些程序和生命周期。
在小程序中App和Page都有他们各自的生命周期函数。
App
Page
骨架屏的目的是为了减缓用户等待的情绪
使用
如图,在小程序开发者工具中,点击这里,生成骨架屏代码
然后他会生成两个文件:index.skeleton.wxml
& index.skeleton.wxss
分别在index.wxml
& index.wxss
引入
总结
优化长列表,使用recycle-view
& recycle-item
虚拟DOM组件,在渲染的时候需要知道每个循环单元的一个高度,这是消费代码需要传递给组件的,在recycle-view组件里面,用户滑动的是数据而不是组件本身。
使用
首先初始化package.json
// npm初始化package.json文件
npm init -y
安装
// 安装 miniprogram-recycle-view插件
npm install --save miniprogram-recycle-view
构建依赖
生成miniprogram_npm
目录
组件内引用
代码中使用
list.wxml
长列表前面的内容
{{item.idx+1}}. {{item.title}}
长列表后面的内容
list.js
const createRecycleContext = require('miniprogram-recycle-view')
Page({
onReady: function () {
var ctx = createRecycleContext({
id: 'recycleId',
dataKey: 'recycleList',
page: this,
itemSize: this.itemSizeFunc
})
ctx.append([{
image_url: 'https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/09883c4389f642829c96b6216eebea85~tplv-k3u1fbpfcp-watermark.image?',
idx: 1,
title: '你好'
}])
// ctx.update(beginIndex, list)
// ctx.destroy()
},
onScroll() {
console.log('scroll')
},
itemSizeFunc: function (item, idx) {
console.log(item)
return {
width: 375,
height: 100
}
}
})
总结
代码写起来不是那么舒服,组件使用不友好,但是对性能确实还不错。
有时候我们在页面上会弹出一些特定的半屏窗口,例如登录窗口。这时候如果用户在iOS设备上使用了左滑手势或者是在Android设备上单击了物理返回键,会造成页面跳转到上一个页面,这在大多数情况下它并不是用户的真实本意,用户可能只是想将当前弹出的半屏的假页面给它关掉。
page-container
页面容器,是不需要引入便可以使用。
使用
...
总结
这个组件其实就是防止用户的左滑
或者按键返回
误操作,导致整个页面回退的问题。
实现动画的方式:
animate.css
代码懒加载
使用
// app.json
"lazyCodeLoading": "requiredComponents"
出现以下提示,代表成功
第一次页面运行时,由微信客户端负责将页面在本地的某个区域缓存起来,下次在真正的页面未加载完成前,先展示这个缓存过的页面。
使用
// xxxx.json
"initialRenderingCache": "static"
与静态初始化渲染缓存,动态初始化渲染缓存可以设置动态缓存的数据,放在onReady
生命周期中
使用
// xxx.json
"initialRenderingCache": "dynamic"
// xxx.js
// 设置动态缓存数据
this.setInitialRenderingCache({
customersList: customerList
})
如果出现以下的信息,不用在意。
在手机里会有一条成功的信息:
update view with init data
小程序包大小规定,单个代码包不超过2MB,总包大小不超过20MB,所以到我们业务庞大,代码量大导致项目大小上升,就必须用分包的形式,解决这个问题。
使用
// app.json
{
"pages": [
// "pages/user/index"
],
"subpackages": [
{
"root": "user",
"pages": [
"pages/user/index"
]
}
]
}
使用
{
"pages": [
// "pages/user/index"
// "pages/goods/index"
],
"subpackages": [
{
"root": "user",
"pages": [
"pages/user/index"
]
},
{
"root": "goods",
"pages": [
"pages/goods/index"
],
"independent": true // 独立分包关键配置
}
]
}
// pages/goods/index.js
const app = getApp({allowDefault: true}) // 独立分包中getApp为空,这里设置默认值
独立分包之后访问独立分包页面,主包和组件是不会被加载的,所以要分实际业务场景,进行分包
使用
"preloadRule": {
// 分包root路径+后面的路径
"goods/pages/detail/index": {
"network": "all",
"packages": [
"__APP__"
]
}
}
看到这个提示,表示成功
总结
root
目录,该目录下所有的文件都会自动被分割到这个分包里分包预加载
使用占位组件延迟加载自定义组件
使用
创建一个名称为index_addons
的一个组件分包
// app.json
"subpackages": [
{
"root": "index_addons",
"pages": []
}
]
修改主页配置
{
"componentPlaceholder": {
"stopwatch": "view" // 欲用作占位组件
}
}
在stopwatch中添加代码
调试
懒注入占位组件调试
如果没有这个选项,请确认是否有配置 "lazyCodeLoading": [requiredComponents]
使用
const { default: getNavList } = await require.async("../../../index_addons/compopnents/get_nav_list.js")
确保wx:key
的值是唯一值
如果列表元素是单一的基本数据类型,并且是唯一的,这时候我们可以直接写成this,这里这个this,就代表当前数据列表里面的数据元素
如果列表元素是静态的,只渲染一次,那么可以直接用index
如果数据是从后台获取的,那么wx:key
需要有一个唯一值
使用catch
代替bind
,减少dataset
的数据运输量,因为我们在大多数情况下,我们的事件不需要冒泡,所以bind
很浪费性能,catch
是不会冒泡的,事件传递某些信息的时候,需要什么传什么,不要一刀切,图省事,传递一个很大的对象,如果需要整个对象,建议使用index
进行传递
view detail
节流顾名思义就是控制某段JS代码的执行频率
function throttle(method, wait = 50) {
let previous = 0
return function(...agrs) {
let context = this
let now = new Date().getTime()
if(now - previouts > wait) {
method.apply(context, args)
previous = now
} else {
console.log("节流少许")
}
}
}
顾名思义就是防止抖动,避免把一次时间当做多次处理,敲击键盘就是一个经常都会遇到的防抖操作场景
function debounce(func, wait = 50) {
let timer = null
return function (...args) {
const contest = this
if(timer) {
clearTimeout(timer)
console.log("防抖少许")
}
timer = setTimeout(() => {
func.call(context, ...args)
}, wait)
}
}
const {default:debounce} = require(../../../library/optimus/debounce.js)
const {default:throttle} = require(../../../library/optimus/throttle.js)
onScrolllToLower: throttle(function(e) {
...
})
onTapCustomerItem: debounce(function(e) {
..
})
官方建议:
总页面节点数少于1000个,节点树深度层级少于30层,子节点数不大于60个
所以,在开发过程中,能用
,就不用
,能用文本
,就不用
,对于循环列表中,能用
就不用
-webkit-overflow-scrolling: touch;;
<button class="submit-btn" hover-class="submit-btn__hover">button>
.submit-btn {
...
}
.submit-btn__hover {
...
}
安装
npm install gulp -g
npm install --save-dev gulp gulp-cleanwxss
配置
/tools/gulpfile.js
const gulp = require("gulp")
const cleanwxss = require("gulp-cleanwxss")
// 处理父目录下的样式文件,输出到当前目录下的dist
gulp.task("default", (done) => {
group.src("../minniprogram/index/pages/*/*.wxss")
.pipe(cleanwxss({ log: true }))
.pipe(gulp.dest("./dist"))
done()
})
// 处理分包下的样式文件
gulp.task("goods", (done) => {
group.src("../minniprogram/goods/pages/*/*.wxss")
.pipe(cleanwxss({ log: true }))
.pipe(gulp.dest("./dist"))
done()
})
执行
gulp default
gulp goods
CV
将生成出来的样式文件复制到对应目录即可
.submit-btn {
...
padding: 10px;
}
.submit-btn {
position: relative;
...
}
.submit-btn::after {
content: '',
position: absolute;
top: -20px;
right: -20px;
bottom: -20px;
left: -20px;
}
clearTimeout(timer1)
// 及时释放本页范围内添加的全局监听器
onLoad() {
wx.onThemeChange(this.themeChangeHandler)
}
onUnload() {
wx.offThemeChange(this.themeChangeHandler)
}
模拟器测试需要在app.json
中添加
"darkMode": "true"
// bad
if(this.data.gender === '1') {
this.setData({
genderName: '男'
})
} else if(this.data.gender === '0') {
this.setData({
genderName: '女'
})
} else {
this.setData({
genderName: '未知'
})
}
// good
let genderName = '未知'
if(this.data.gender === '1') genderName = '男'
if(this.data.gender === '0') genderName = '女'
this.setData({
genderName,
})
Page({
allList: [],
pageNum: 1,
pageSize: 10,
data: {
list: []
}
})
this.setData({
[this.data.customerList[index].title]: item.title+='---'
})
技术点:Storage
enableCache: true,
enableHttp2: true,
enableQuic: true
npm i priority-async-queue
request.js
const PriorityAsyncQueue = require('priority-async-queue')
const queue = new PriorityAsyncQueue(10) // default 10
const low = "low", normal = "normal", mid = "mid", high = "high", "urgent" = "urgent"
export const priority = { low, normal, mid, high, urgent }
....
return new Promise((resolve, reject) => {
queue.addTask({ priority }. () => {
wx.request(Object.assgin(args,{
success: resolve,
fail: reject
})
})
})
小程序的优化大致就是从这些方面入手了,觉得有用的同学,点个赞,收个藏吧!