mpvue性能优化实战技巧(小结)
最近一直在折腾mpvue
写的微信小程序的性能优化,分享下实战的过程。
先上个优化前后的图:
可以看到打包后的代码量从813kb
减少到387kb
,audits体验评分从b
到a
,效果还是比较明显的。其实这个指标说明不了什么,而且轻易就可以做到,更重要的是优化小程序运行过程中的卡顿感,请耐心往下看。
常规优化
常规的web端优化方法在小程序中也是适用的,而且不可忽视。
一、压缩图片
这一步最简单,但是容易被忽视。在上在线压缩,然后下载替换即可。
我这项目的压缩率高达72%
,可以说打包后的代码从813kb
降到387kb
大部分都是归功于压缩图片了。
二、移除无用的库
我之前在项目中使用了vant weapp,在static
目录下引入了整个库,但实际上我只使用了button
,field
,dialog
等几个组件,实在是没必要。
所以干脆移除掉了,微信小程序自身提供的button
,wx.showmodal
等一些组件基本可以满足需求,自己手写一下样式也不用花什么时间。
在这里建议大家,在微信小程序中,尽量避免使用过多的依赖库。
不要贪图方便而引入一些比较大的库,小程序不同于web
,限制比较多,能自己写一下就尽量自己写一下吧。
小程序的优化
咱们首先得看一下,大多是围绕这个建议去做。
一、开启vue.config._mptrace = true
这个是mpvue
性能优化的一个黑科技啊,可能大多数同学都不知道这个,我在官方文档都没有搜到到这个配置,我真的是服了。我能找到这个配置也是google机缘巧合下看到的,出处:
具体做法是在/src/main.js
添加vue.config._mptrace = true
,如:
vue.config._mptrace = true vue.config.productiontip = false app.mptype = 'app'
添加了vue.config._mptrace
属性,这样就可以看到console里会打印每500ms更新的数据量。如图:
如果数据更新量很大,会明显感觉小程序运行卡顿,反之就流畅。因此我们可以根据这个指标,逐步找出性能瓶颈并解决掉。
二、精简data
1. 过滤api返回的冗余数据
后端的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: 在我个人的实际操作中,做数据过滤虽然增加了维护的成本,但一般收益都很大,因次这个方法比较推荐。
2. data()中只放需要的数据
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
了。
3. 静态图片放进static
这个问题和上面的问题其实是一样的,有时候我们会通过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
组件性能上不是很好,使用时要注意。参考着两个思路:
在我使用时,由于需求原因,动态删掉swiper-item的思路不可行(手滑时会造成抖动)。因此只能作罢。但仍然可以优化一下:
将未显示的swiper-item
中的图片用v-if
隐藏到,当判断到current时才显示,防止大量图片的渲染导致的性能问题。
四、vuex使用注意事项
我之前写过的一篇里面有讲如何在小程序中使用vuex
。但遇到了个比较严重的性能问题。
1. 问题描述
我开发的是一个音频类的小程序,所以需要将播放列表playlist
,当前索引currentindex
和当前时长currenttime
放进state.js
中:
const state = { currentindex: 0, // playlist当前索引 currenttime: 0, // 当前播放的进度 playlist: [], // {title: '', url: '', singer: ''} }
每次用户点击播放音频时,都会先加载音频的播放列表playlist
,然后播放时更新当前时长currenttime
,发现有时候播音频时整个小程序非常卡顿。
注意到,音频需每秒就得更新一次currenttime
,即每秒就做一次setdata
操作,稍微有些卡顿是可以理解的。但我发现是播放列表数据比较多时会特别卡,比如playlist的长度是100条以上时。
2. 问题原因
我开启vue.config._mptrace = true
后发现一个规律:
当palylist
数据量小时,console
显示造成的数据量更新数值比较小;当playlist
比较大时,console
显示造成的数据量更新数值比较大。
ps: 我曾尝试将playlist数据量增加到200条,每500ms的数据量更新达到800kb左右。
到这里基本可以确定一个事实就是:更新state
中的任何一个字段,将导致整个state
全量一起setdata
。在我这里的例子,虽然我每次只是更新currenttime
这个字段的值,但依然导致将state中的其他字段如playlist
,currentindex
都一起做了一次setdata
操作。
3. 解决问题
有两个思路:
精简state中保存的数据,即限制playlist
的数据不能太多,可将一些数据暂存在storage
中
vuex
采用module
的写法能改善这个问题,虽然使用时命名空间造成一定的麻烦。
一般情况下,推荐使用后者。我在项目中尝试使用了前者,同样能达到很好的效果,请继续看下面的分享。
五、善用storage
1.为什么说要善用storage
由于小程序的内存非常宝贵,占用内存过大会非常卡顿,因此最好尽可能少的将数据放到内存中,即vuex
存的数据要尽可能少。而小程序的storage
支持单个 key允许存储的最大数据长度为 1mb
,所有数据存储上限为 10mb
。
所以可以将一些相对取用不频繁的数据放进storage
中,需要时再将这些数据放进内存,从而缓解内存的紧张,有点类似windows中虚拟内存
的概念。
2.storage换内存的实例
这个例子讲的会有点啰嗦,真正能用到的朋友可以详细看下。
上面讲到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条数据:
- 当前音频是列表中的第1条(索引是0),截取前5个:
playlist.splice(0, 5)
,此时currentaudio
在这5个数据的索引是0
,没有上一首
,有4个下一首
- 当前音频是列表中的第2条(索引是1),截取前5个:
playlist.splice(0, 5)
,此时currentaudio
在这5个数据的索引是1
,有1个上一首
,3个下一首
- 当前音频是列表中的第3条(索引是2),截取前5个:
playlist.splice(0, 5)
,此时currentaudio
在这5个数据的索引是2
,有2个上一首
,2个下一首
- 当前音频是列表中的第4条(索引是3),截取第1到6个:
playlist.splice(1, 5)
- ,此时
currentaudio
在这5个数据的索引是2
,有2个上一首
,2个下一首
- 当前音频是列表中的第5条(索引是4),截取第2到7个:
playlist.splice(2, 5)
,此时currentaudio
在这5个数据的索引是2
,有2个上一首
,2个下一首
- ...
- 当前音频是列表中的第9条(索引是
8
),截取后5个:playlist.splice(4, 5)
,此时currentaudio
在这5个数据的索引是3
,有3个上一首
,1个下一首
- 当前音频是列表中的最后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中的数据精简后,我所做的小程序在播放音频时刷其他页面已经非常流畅啦,效果非常好。
六、动画优化
这个问题在已经讲过了,感兴趣的可以移步看一眼,这里只写下概述:
如果要使用动画,尽量用css动画代替wx.createanimation使用css动画时建议开启硬件加速最后
大致总结一下上面所讲的几个要点:
- 开发时打开
vue.config._mptrace = true。
- 谨慎引入第三方库,权衡收益。
- 添加数据到data中时要克制,能精简尽量精简。
- 图片记得要压缩,图片在显示时才渲染。
- vuex保持数据精简,必要时可先存storage。
性能优化是一个永不止步的话题,我也还在摸索,不足之处还请大家指点和分享。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。