最近一直在折腾mpvue
写的微信小程序的性能优化,分享下实战的过程。javascript
先上个优化先后的图: css
813KB
减小到
387KB
,Audits体验评分从
B
到
A
,效果仍是比较明显的。其实这个指标说明不了什么,并且轻易就能够作到,更重要的是优化
小程序运行过程当中的卡顿感,请耐心往下看。
常规的Web端优化方法在小程序中也是适用的,并且不可忽视。html
这一步最简单,可是容易被忽视。在tiny上在线压缩,而后下载替换便可。前端
72%
,能够说打包后的代码从
813KB
降到
387KB
大部分都是归功于压缩图片了。
我以前在项目中使用了Vant Weapp,在static
目录下引入了整个库,但实际上我只使用了button
,field
,dialog
等几个组件,实在是不必。vue
因此干脆移除掉了,微信小程序自身提供的button
,wx.showModal
等一些组件基本能够知足需求,本身手写一下样式也不用花什么时间。java
在这里建议你们,在微信小程序中,尽可能避免使用过多的依赖库。git
不要贪图方便而引入一些比较大的库,小程序不一样于Web
,限制比较多,能本身写一下就尽可能本身写一下吧。github
我们首先得看一下官方优化建议,大可能是围绕这个建议去作。vuex
这个是
mpvue
性能优化的一个黑科技啊,可能大多数同窗都不知道这个,我在官方文档都没有搜到到这个配置,我真的是服了。小程序
我能找到这个配置也是Google机缘巧合下看到的,出处:mpvue重要更新,页面更新机制进行全面升级 具体作法是在
/src/main.js
添加Vue.config._mpTrace = true
,如:
Vue.config._mpTrace = true
Vue.config.productionTip = false
App.mpType = 'app'
复制代码
添加了Vue.config._mpTrace
属性,这样就能够看到console里会打印每500ms更新的数据量。如图:
后端的api多是须要同时为iOS,Android,H5等提供服务的,每每会有些冗余的数据小程序是用不到的。好比api返回的一个文章列表
数据有不少字段:
this.articleList = [
{
articleId: 1,
desc: 'xxxxxx',
author: 'fengxianqi',
time: 'xxx',
comments: [
{
userId: 2,
conent: 'xxx'
}
]
},
{
articleId: 2
// ...
},
// ...
]
复制代码
假设咱们在小程序中只须要用到列表中的部分字段,若是不对数据作处理,将整个articleList
都setData
进去,是不明智的。
小程序官方文档: 单次设置的数据不能超过1024kB,请尽可能避免一次设置过多的数据。
能够看出,内存是很宝贵的,当articleList
数据量很是大超过1M时,某些机型就会爆掉(我在iOS中遇到过不少次)。
所以,须要将接口返回的数据剔除掉不须要的,再setData
,回到咱们上面的articleList
例子,假设咱们只须要用articleId
和author
这两个字段,能够这样:
import { getArticleList } from '@/api/article'
export default {
data () {
return {
articleList: []
}
}
methods: {
getList () {
getArticleList().then(res => {
let rawList = res.list
this.articleList = this.simplifyArticleList(rawList)
})
},
simplifyArticleList (list) {
return list.map(item => {
return {
articleId: item.articleId,
author: item.author
// 须要哪些字段就加上哪些字段
}
})
}
}
}
复制代码
这里咱们将返回的数据经过simplifyArticleList
来精简数据,此时过滤后的articleList
中的数据相似:
[
{articleId: 1, author: 'fengxianqi'},
{articleId: 2, author: 'others'}
// ...
]
复制代码
固然,若是你的需求中是全部数据都要用到(或者大部分数据),就不必作一层精简了,收益不大。毕竟精简数据的函数中具体的字段,是会增长维护成本的。
PS: 在我我的的实际操做中,作数据过滤虽然增长了维护的成本,但通常收益都很大,因次这个方法比较推荐。
import xx from 'xx.js'
export default {
data () {
return {
xx,
otherXX: '2'
}
}
}
复制代码
有些同窗可能会习惯将import
的东西都先放进data
中,再在methods
中使用,在小程序中可能是个很差的习惯。
由于经过Vue.config._mpTrace = true
在更新某个数据时,我对比放进data和不放进data中的两种状况会有差异。
因此我猜想多是data是会一块儿更新的,好比只是想更新otherXX
时,会同时将xx
也一块儿合起来setData
了。
这个问题和上面的问题实际上是同样的,有时候咱们会经过import
的方式引入,好比这样:
<template>
<img :src="UserIcon">
</template>
<script>
import UserIcon from '@/assets/images/user_icon.png'
export default {
data () {
return {
UserIcon
}
}
}
</script>
复制代码
这样会致使打包后的代码,图片是base64
形式(很长的一段字符串)存放在data
中,不利于精简data。同时当该组件多个地方使用时,每一个组件实例都会携带这一段很长的base64
代码,进一步致使数据的冗余。
所以,建议将静态图片放到static
目录下,这样引用:
<template>
<img src="/static/images/user_icon.png">
</template>
复制代码
代码也更简洁清爽。
看一下作了上面操做的先后对比图,使用体验上也流畅了不少。
小程序自身提供的swiper
组件性能上不是很好,使用时要注意。参考着两个思路:
在我使用时,因为需求缘由,动态删掉swiper-item的思路不可行(手滑时会形成抖动)。所以只能做罢。但仍然能够优化一下:
swiper-item
中的图片用v-if
隐藏到,当判断到current时才显示,防止大量图片的渲染致使的性能问题。我以前写过的一篇mpvue开发音频类小程序踩坑和建议里面有讲如何在小程序中使用vuex
。但遇到了个比较严重的性能问题。
我开发的是一个音频类的小程序,因此须要将播放列表playList
,当前索引currentIndex
和当前时长currentTime
放进state.js
中:
const state = {
currentIndex: 0, // playList当前索引
currentTime: 0, // 当前播放的进度
playList: [], // {title: '', url: '', singer: ''}
}
复制代码
每次用户点击播放音频时,都会先加载音频的播放列表playList
,而后播放时更新当前时长currentTime
,发现有时候播音频时整个小程序很是卡顿。
注意到,音频需每秒就得更新一次
currentTime
,即每秒就作一次setData
操做,稍微有些卡顿是能够理解的。但我发现是播放列表数据比较多时会特别卡,好比playList的长度是100条以上时。
我开启Vue.config._mpTrace = true
后发现一个规律:
当palyList
数据量小时,console
显示形成的数据量更新数值比较小;当playList
比较大时,console
显示形成的数据量更新数值比较大。
PS: 我曾尝试将playList数据量增长到200条,每500ms的数据量更新达到800KB左右。
到这里基本能够肯定一个事实就是:更新state
中的任何一个字段,将致使整个state
全量一块儿setData
。在我这里的例子,虽然我每次只是更新currentTime
这个字段的值,但依然致使将state中的其余字段如playList
,currentIndex
都一块儿作了一次setData
操做。
有两个思路:
playList
的数据不能太多,可将一些数据暂存在storage
中vuex
采用Module
的写法能改善这个问题,虽然使用时命名空间形成必定的麻烦。vuex传送门通常状况下,推荐使用后者。我在项目中尝试使用了前者,一样能达到很好的效果,请继续看下面的分享。
因为小程序的内存很是宝贵,占用内存过大会很是卡顿,所以最好尽量少的将数据放到内存中,即vuex
存的数据要尽量少。而小程序的storage
支持单个 key容许存储的最大数据长度为 1MB
,全部数据存储上限为 10MB
。
因此能够将一些相对取用不频繁的数据放进storage
中,须要时再将这些数据放进内存,从而缓解内存的紧张,有点相似Windows中虚拟内存
的概念。
这个例子讲的会有点啰嗦,真正能用到的朋友能够详细看下。
上面讲到playList
数据量太多,播放一条音频时其实只须要最多保证3条数据在内存中便可,即上一首
,播放中的
,下一首
,咱们能够将多余的播放列表存放在storage
中。
PS: 为了保证更平滑地连续切换下一首,咱们能够稍微保存多几条,好比我这里选择保存5条数据在vuex中,播放时始终保证当前播放的音频先后都有两条数据。
// 首次播放背景音频的方法
async function playAudio (audioId) {
// 拿到播放列表,此时的playList最多只有5条数据。getPlayList方法看下面
const playList = await getPlayList(audioId)
// 当前音频在vuex中的currentIndex
const currentIndex = playList.findIndex(item => item.audioId === audioId)
// 播放背景音频
this.audio = wx.getBackgroundAudioManager()
this.audio.title = playList[currentIndex].title
this.audio.src = playList[currentIndex].url
// 经过mapActions将播放列表和currentIndex更新到vuex中
this.updateCurrentIndex(index)
this.updatePlayList(playList)
// updateCurrentIndex和updatePlayList是vuex写好的方法
}
// 播放音频时获取播放列表的方法,将全部数据存在storage,而后返回当前音频的先后2条数据,保证最多5条数据
import { loadPlayList } from '@/api/audio'
async function getPlayList (courseId, currentAudioId) {
// 从api中请求获得播放列表
// loadPlayList是api的方法, courseId是获取列表的参数,表示当前课程下的播放列表
let rawList = await loadPlayList(courseId)
// simplifyPlayList过滤掉一些字段
const list = this.simplifyPlayList(rawList)
// 将列表存到storage中
wx.setStorage({
key: 'playList',
data: list
})
return subPlayList(list, currentAudioId)
}
复制代码
重点是subPlayList
方法,这个方法保证了拿到的播放列表是最多5条数据。
function subPlayList(playList, currentAudioId) {
let tempArr = [...playList]
const count = 5 // 保持vuex中最多5条数据
const middle = parseInt(count / 2) // 中点的索引
const len = tempArr.length
// 若是整个原始的播放列表原本就少于5条数据,说明不须要裁剪,直接返回
if (len <= count) {
return tempArr
}
// 找到当前要播放的音频的所在位置
const index = tempArr.findIndex(item => item.audioId === currentAudioId)
// 截取当前音频的先后两条数据
tempArr = tempArr.splice(Math.max(0, Math.min(len - count, index - middle)), count)
return tempArr
}
复制代码
tempArr.splice(Math.max(0, index - middle), count)
可能有些同窗比较难理解,须要仔细琢磨一下。假设playList
有10条数据:
playList.splice(0, 5)
,此时currentAudio
在这5个数据的索引是0
,没有上一首
,有4个下一首
playList.splice(0, 5)
,此时currentAudio
在这5个数据的索引是1
,有1个上一首
,3个下一首
playList.splice(0, 5)
,此时currentAudio
在这5个数据的索引是2
,有2个上一首
,2个下一首
playList.splice(1, 5)
,此时currentAudio
在这5个数据的索引是2
,有2个上一首
,2个下一首
playList.splice(2, 5)
,此时currentAudio
在这5个数据的索引是2
,有2个上一首
,2个下一首
8
),截取后5个:playList.splice(4, 5)
,此时currentAudio
在这5个数据的索引是3
,有3个上一首
,1个下一首
9
),截取后的5个:playList.splice(4, 5)
,此时currentAudio
在这5个数据的索引是4
,有4个上一首
,没有下一首
有点啰嗦,感兴趣的同窗仔细琢磨下,不管当前音频在哪,都始终保证了拿到当前音频先后的最多5条数据。
接下来就是维护播放上一首或下一首时保证当前vuex中的playList
始终是包含当前音频的先后2条。
function playNextAudio() {
const nextIndex = this.currentIndex + 1
if (nextIndex < this.playList.length) {
// 没有超出数组长度,说明在vuex的列表中,能够直接播放
this.audio = wx.getBackgroundAudioManager()
this.audio.src = this.playList[nextIndex].url
this.audio.title = this.playList[nextIndex].title
this.updateCurrentIndex(nextIndex)
// 当判断到已经到vuex的playList的边界了,从新从storage中拿数据补充到playList
if (nextIndex === this.playList.length - 1 || nextIndex === 0) {
// 拿到只有当前音频先后最多5条数据的列表
const newList = getPlayList(this.playList[nextIndex].courseId, this.playList[nextIndex].audioId)
// 当前音频在这5条数据中的索引
const index = newList.findIndex(item => item.audioId === this.playList[nextIndex].audioId)
// 更新到vuex
this.updateCurrentIndex(index)
this.updatePlayList(newList)
}
}
}
复制代码
这里的getPlayList
方法是上面讲过的,原本是从api中直接获取的,为了不每次都从api直接获取,因此须要改一下,先读storage,若无则从api获取:
import { loadPlayList } from '@/api/audio'
async function getPlayList (courseId, currentAudioId) {
// 先从缓存列表中拿
const playList = wx.getStorageSync('playList')
if (playList && playList.length > 0 && courseId === playList[0].courseId) {
// 命中缓存,则从直接返回
return subPlayList(playList, currentAudioId)
} else {
// 没有命中缓存,则从api中获取
const list = await loadPlayList(courseId)
wx.setStorage({
key: 'playList',
data: list
})
return subPlayList(list, currentAudioId)
}
}
复制代码
播放上一首也是同理,就不赘述了。
PS: 将vuex中的数据精简后,我所作的小程序在播放音频时刷其余页面已经很是流畅啦,效果很是好。
这个问题在mpvue开发音频类小程序踩坑和建议已经讲过了,感兴趣的能够移步看一眼,这里只写下概述:
大体总结一下上面所讲的几个要点:
Vue.config._mpTrace = true
。性能优化是一个永不止步的话题,我也还在摸索,不足之处还请你们指点和分享。
欢迎关注,会持续分享前端实战中遇到的一些问题和解决办法。