mpvue性能優(yōu)化實(shí)戰(zhàn)技巧(小結(jié))
最近一直在折騰mpvue
寫(xiě)的微信小程序的性能優(yōu)化,分享下實(shí)戰(zhàn)的過(guò)程。
先上個(gè)優(yōu)化前后的圖:
可以看到打包后的代碼量從813KB
減少到387KB
,Audits體驗(yàn)評(píng)分從B
到A
,效果還是比較明顯的。其實(shí)這個(gè)指標(biāo)說(shuō)明不了什么,而且輕易就可以做到,更重要的是優(yōu)化小程序運(yùn)行過(guò)程中的卡頓感,請(qǐng)耐心往下看。
常規(guī)優(yōu)化
常規(guī)的Web端優(yōu)化方法在小程序中也是適用的,而且不可忽視。
一、壓縮圖片
這一步最簡(jiǎn)單,但是容易被忽視。在tiny上在線壓縮,然后下載替換即可。
我這項(xiàng)目的壓縮率高達(dá)72%
,可以說(shuō)打包后的代碼從813KB
降到387KB
大部分都是歸功于壓縮圖片了。
二、移除無(wú)用的庫(kù)
我之前在項(xiàng)目中使用了Vant Weapp,在static
目錄下引入了整個(gè)庫(kù),但實(shí)際上我只使用了button
,field
,dialog
等幾個(gè)組件,實(shí)在是沒(méi)必要。
所以干脆移除掉了,微信小程序自身提供的button
,wx.showModal
等一些組件基本可以滿足需求,自己手寫(xiě)一下樣式也不用花什么時(shí)間。
在這里建議大家,在微信小程序中,盡量避免使用過(guò)多的依賴庫(kù)。
不要貪圖方便而引入一些比較大的庫(kù),小程序不同于Web
,限制比較多,能自己寫(xiě)一下就盡量自己寫(xiě)一下吧。
小程序的優(yōu)化
咱們首先得看一下官方優(yōu)化建議,大多是圍繞這個(gè)建議去做。
一、開(kāi)啟Vue.config._mpTrace = true
這個(gè)是mpvue
性能優(yōu)化的一個(gè)黑科技啊,可能大多數(shù)同學(xué)都不知道這個(gè),我在官方文檔都沒(méi)有搜到到這個(gè)配置,我真的是服了。我能找到這個(gè)配置也是Google機(jī)緣巧合下看到的,出處:mpvue重要更新,頁(yè)面更新機(jī)制進(jìn)行全面升級(jí)
具體做法是在/src/main.js
添加Vue.config._mpTrace = true
,如:
Vue.config._mpTrace = true Vue.config.productionTip = false App.mpType = 'app'
添加了Vue.config._mpTrace
屬性,這樣就可以看到console里會(huì)打印每500ms更新的數(shù)據(jù)量。如圖:
如果數(shù)據(jù)更新量很大,會(huì)明顯感覺(jué)小程序運(yùn)行卡頓,反之就流暢。因此我們可以根據(jù)這個(gè)指標(biāo),逐步找出性能瓶頸并解決掉。
二、精簡(jiǎn)data
1. 過(guò)濾api返回的冗余數(shù)據(jù)
后端的api可能是需要同時(shí)為iOS,Android,H5等提供服務(wù)的,往往會(huì)有些冗余的數(shù)據(jù)小程序是用不到的。比如api返回的一個(gè)文章列表
數(shù)據(jù)有很多字段:
this.articleList = [ { articleId: 1, desc: 'xxxxxx', author: 'fengxianqi', time: 'xxx', comments: [ { userId: 2, conent: 'xxx' } ] }, { articleId: 2 // ... }, // ... ]
假設(shè)我們?cè)谛〕绦蛑兄恍枰玫搅斜碇械牟糠肿侄危绻粚?duì)數(shù)據(jù)做處理,將整個(gè)articleList
都setData
進(jìn)去,是不明智的。
小程序官方文檔:單次設(shè)置的數(shù)據(jù)不能超過(guò)1024kB,請(qǐng)盡量避免一次設(shè)置過(guò)多的數(shù)據(jù)。
可以看出,內(nèi)存是很寶貴的,當(dāng)articleList
數(shù)據(jù)量非常大超過(guò)1M時(shí),某些機(jī)型就會(huì)爆掉(我在iOS中遇到過(guò)很多次)。
因此,需要將接口返回的數(shù)據(jù)剔除掉不需要的,再setData
,回到我們上面的articleList
例子,假設(shè)我們只需要用articleId
和author
這兩個(gè)字段,可以這樣:
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 // 需要哪些字段就加上哪些字段 } }) } } }
這里我們將返回的數(shù)據(jù)通過(guò)simplifyArticleList
來(lái)精簡(jiǎn)數(shù)據(jù),此時(shí)過(guò)濾后的articleList
中的數(shù)據(jù)類似:
[ {articleId: 1, author: 'fengxianqi'}, {articleId: 2, author: 'others'} // ... ]
當(dāng)然,如果你的需求中是所有數(shù)據(jù)都要用到(或者大部分?jǐn)?shù)據(jù)),就沒(méi)必要做一層精簡(jiǎn)了,收益不大。畢竟精簡(jiǎn)數(shù)據(jù)的函數(shù)中具體的字段,是會(huì)增加維護(hù)成本的。
PS: 在我個(gè)人的實(shí)際操作中,做數(shù)據(jù)過(guò)濾雖然增加了維護(hù)的成本,但一般收益都很大,因次這個(gè)方法比較推薦。
2. data()中只放需要的數(shù)據(jù)
import xx from 'xx.js' export default { data () { return { xx, otherXX: '2' } } }
有些同學(xué)可能會(huì)習(xí)慣將import
的東西都先放進(jìn)data
中,再在methods
中使用,在小程序中可能是個(gè)不好的習(xí)慣。
因?yàn)橥ㄟ^(guò)Vue.config._mpTrace = true
在更新某個(gè)數(shù)據(jù)時(shí),我對(duì)比放進(jìn)data和不放進(jìn)data中的兩種情況會(huì)有差別。
所以我猜測(cè)可能是data是會(huì)一起更新的,比如只是想更新otherXX
時(shí),會(huì)同時(shí)將xx
也一起合起來(lái)setData
了。
3. 靜態(tài)圖片放進(jìn)static
這個(gè)問(wèn)題和上面的問(wèn)題其實(shí)是一樣的,有時(shí)候我們會(huì)通過(guò)import
的方式引入,比如這樣:
<template> <img :src="UserIcon"> </template> <script> import UserIcon from '@/assets/images/user_icon.png' export default { data () { return { UserIcon } } } </script>
這樣會(huì)導(dǎo)致打包后的代碼,圖片是base64
形式(很長(zhǎng)的一段字符串)存放在data
中,不利于精簡(jiǎn)data。同時(shí)當(dāng)該組件多個(gè)地方使用時(shí),每個(gè)組件實(shí)例都會(huì)攜帶這一段很長(zhǎng)的base64
代碼,進(jìn)一步導(dǎo)致數(shù)據(jù)的冗余。
因此,建議將靜態(tài)圖片放到static
目錄下,這樣引用:
<template> <img src="/static/images/user_icon.png"> </template>
代碼也更簡(jiǎn)潔清爽。
看一下做了上面操作的前后對(duì)比圖,使用體驗(yàn)上也流暢了很多。
三、swiper優(yōu)化
小程序自身提供的swiper
組件性能上不是很好,使用時(shí)要注意。參考著兩個(gè)思路:
【優(yōu)化】解決swiper渲染很多圖片時(shí)的卡頓
想請(qǐng)教一下小程序swiper組件的問(wèn)題
在我使用時(shí),由于需求原因,動(dòng)態(tài)刪掉swiper-item的思路不可行(手滑時(shí)會(huì)造成抖動(dòng))。因此只能作罷。但仍然可以優(yōu)化一下:
將未顯示的swiper-item
中的圖片用v-if
隱藏到,當(dāng)判斷到current時(shí)才顯示,防止大量圖片的渲染導(dǎo)致的性能問(wèn)題。
四、vuex使用注意事項(xiàng)
我之前寫(xiě)過(guò)的一篇mpvue開(kāi)發(fā)音頻類小程序踩坑和建議里面有講如何在小程序中使用vuex
。但遇到了個(gè)比較嚴(yán)重的性能問(wèn)題。
1. 問(wèn)題描述
我開(kāi)發(fā)的是一個(gè)音頻類的小程序,所以需要將播放列表playList
,當(dāng)前索引currentIndex
和當(dāng)前時(shí)長(zhǎng)currentTime
放進(jìn)state.js
中:
const state = { currentIndex: 0, // playList當(dāng)前索引 currentTime: 0, // 當(dāng)前播放的進(jìn)度 playList: [], // {title: '', url: '', singer: ''} }
每次用戶點(diǎn)擊播放音頻時(shí),都會(huì)先加載音頻的播放列表playList
,然后播放時(shí)更新當(dāng)前時(shí)長(zhǎng)currentTime
,發(fā)現(xiàn)有時(shí)候播音頻時(shí)整個(gè)小程序非常卡頓。
注意到,音頻需每秒就得更新一次currentTime
,即每秒就做一次setData
操作,稍微有些卡頓是可以理解的。但我發(fā)現(xiàn)是播放列表數(shù)據(jù)比較多時(shí)會(huì)特別卡,比如playList的長(zhǎng)度是100條以上時(shí)。
2. 問(wèn)題原因
我開(kāi)啟Vue.config._mpTrace = true
后發(fā)現(xiàn)一個(gè)規(guī)律:
當(dāng)palyList
數(shù)據(jù)量小時(shí),console
顯示造成的數(shù)據(jù)量更新數(shù)值比較??;當(dāng)playList
比較大時(shí),console
顯示造成的數(shù)據(jù)量更新數(shù)值比較大。
PS: 我曾嘗試將playList數(shù)據(jù)量增加到200條,每500ms的數(shù)據(jù)量更新達(dá)到800KB左右。
到這里基本可以確定一個(gè)事實(shí)就是:更新state
中的任何一個(gè)字段,將導(dǎo)致整個(gè)state
全量一起setData
。在我這里的例子,雖然我每次只是更新currentTime
這個(gè)字段的值,但依然導(dǎo)致將state中的其他字段如playList
,currentIndex
都一起做了一次setData
操作。
3. 解決問(wèn)題
有兩個(gè)思路:
精簡(jiǎn)state中保存的數(shù)據(jù),即限制playList
的數(shù)據(jù)不能太多,可將一些數(shù)據(jù)暫存在storage
中
vuex
采用Module
的寫(xiě)法能改善這個(gè)問(wèn)題,雖然使用時(shí)命名空間造成一定的麻煩。vuex傳送門
一般情況下,推薦使用后者。我在項(xiàng)目中嘗試使用了前者,同樣能達(dá)到很好的效果,請(qǐng)繼續(xù)看下面的分享。
五、善用storage
1.為什么說(shuō)要善用storage
由于小程序的內(nèi)存非常寶貴,占用內(nèi)存過(guò)大會(huì)非??D,因此最好盡可能少的將數(shù)據(jù)放到內(nèi)存中,即vuex
存的數(shù)據(jù)要盡可能少。而小程序的storage
支持單個(gè) key允許存儲(chǔ)的最大數(shù)據(jù)長(zhǎng)度為 1MB
,所有數(shù)據(jù)存儲(chǔ)上限為 10MB
。
所以可以將一些相對(duì)取用不頻繁的數(shù)據(jù)放進(jìn)storage
中,需要時(shí)再將這些數(shù)據(jù)放進(jìn)內(nèi)存,從而緩解內(nèi)存的緊張,有點(diǎn)類似Windows中虛擬內(nèi)存
的概念。
2.storage換內(nèi)存的實(shí)例
這個(gè)例子講的會(huì)有點(diǎn)啰嗦,真正能用到的朋友可以詳細(xì)看下。
上面講到playList
數(shù)據(jù)量太多,播放一條音頻時(shí)其實(shí)只需要最多保證3條數(shù)據(jù)在內(nèi)存中即可,即上一首
,播放中的
,下一首
,我們可以將多余的播放列表存放在storage
中。
PS: 為了保證更平滑地連續(xù)切換下一首,我們可以稍微保存多幾條,比如我這里選擇保存5條數(shù)據(jù)在vuex中,播放時(shí)始終保證當(dāng)前播放的音頻前后都有兩條數(shù)據(jù)。
// 首次播放背景音頻的方法 async function playAudio (audioId) { // 拿到播放列表,此時(shí)的playList最多只有5條數(shù)據(jù)。getPlayList方法看下面 const playList = await getPlayList(audioId) // 當(dāng)前音頻在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 // 通過(guò)mapActions將播放列表和currentIndex更新到vuex中 this.updateCurrentIndex(index) this.updatePlayList(playList) // updateCurrentIndex和updatePlayList是vuex寫(xiě)好的方法 } // 播放音頻時(shí)獲取播放列表的方法,將所有數(shù)據(jù)存在storage,然后返回當(dāng)前音頻的前后2條數(shù)據(jù),保證最多5條數(shù)據(jù) import { loadPlayList } from '@/api/audio' async function getPlayList (courseId, currentAudioId) { // 從api中請(qǐng)求得到播放列表 // loadPlayList是api的方法, courseId是獲取列表的參數(shù),表示當(dāng)前課程下的播放列表 let rawList = await loadPlayList(courseId) // simplifyPlayList過(guò)濾掉一些字段 const list = this.simplifyPlayList(rawList) // 將列表存到storage中 wx.setStorage({ key: 'playList', data: list }) return subPlayList(list, currentAudioId) }
重點(diǎn)是subPlayList
方法,這個(gè)方法保證了拿到的播放列表是最多5條數(shù)據(jù)。
function subPlayList(playList, currentAudioId) { let tempArr = [...playList] const count = 5 // 保持vuex中最多5條數(shù)據(jù) const middle = parseInt(count / 2) // 中點(diǎn)的索引 const len = tempArr.length // 如果整個(gè)原始的播放列表本來(lái)就少于5條數(shù)據(jù),說(shuō)明不需要裁剪,直接返回 if (len <= count) { return tempArr } // 找到當(dāng)前要播放的音頻的所在位置 const index = tempArr.findIndex(item => item.audioId === currentAudioId) // 截取當(dāng)前音頻的前后兩條數(shù)據(jù) tempArr = tempArr.splice(Math.max(0, Math.min(len - count, index - middle)), count) return tempArr }
tempArr.splice(Math.max(0, index - middle), count)
可能有些同學(xué)比較難理解,需要仔細(xì)琢磨一下。假設(shè)playList
有10條數(shù)據(jù):
- 當(dāng)前音頻是列表中的第1條(索引是0),截取前5個(gè):
playList.splice(0, 5)
,此時(shí)currentAudio
在這5個(gè)數(shù)據(jù)的索引是0
,沒(méi)有上一首
,有4個(gè)下一首
- 當(dāng)前音頻是列表中的第2條(索引是1),截取前5個(gè):
playList.splice(0, 5)
,此時(shí)currentAudio
在這5個(gè)數(shù)據(jù)的索引是1
,有1個(gè)上一首
,3個(gè)下一首
- 當(dāng)前音頻是列表中的第3條(索引是2),截取前5個(gè):
playList.splice(0, 5)
,此時(shí)currentAudio
在這5個(gè)數(shù)據(jù)的索引是2
,有2個(gè)上一首
,2個(gè)下一首
- 當(dāng)前音頻是列表中的第4條(索引是3),截取第1到6個(gè):
playList.splice(1, 5)
- ,此時(shí)
currentAudio
在這5個(gè)數(shù)據(jù)的索引是2
,有2個(gè)上一首
,2個(gè)下一首
- 當(dāng)前音頻是列表中的第5條(索引是4),截取第2到7個(gè):
playList.splice(2, 5)
,此時(shí)currentAudio
在這5個(gè)數(shù)據(jù)的索引是2
,有2個(gè)上一首
,2個(gè)下一首
- ...
- 當(dāng)前音頻是列表中的第9條(索引是
8
),截取后5個(gè):playList.splice(4, 5)
,此時(shí)currentAudio
在這5個(gè)數(shù)據(jù)的索引是3
,有3個(gè)上一首
,1個(gè)下一首
- 當(dāng)前音頻是列表中的最后1條(索引是
9
),截取后的5個(gè):playList.splice(4, 5)
,此時(shí)currentAudio
在這5個(gè)數(shù)據(jù)的索引是4
,有4個(gè)上一首
,沒(méi)有下一首
有點(diǎn)啰嗦,感興趣的同學(xué)仔細(xì)琢磨下,無(wú)論當(dāng)前音頻在哪,都始終保證了拿到當(dāng)前音頻前后的最多5條數(shù)據(jù)。
接下來(lái)就是維護(hù)播放上一首或下一首時(shí)保證當(dāng)前vuex中的playList
始終是包含當(dāng)前音頻的前后2條。
播放下一首
function playNextAudio() { const nextIndex = this.currentIndex + 1 if (nextIndex < this.playList.length) { // 沒(méi)有超出數(shù)組長(zhǎng)度,說(shuō)明在vuex的列表中,可以直接播放 this.audio = wx.getBackgroundAudioManager() this.audio.src = this.playList[nextIndex].url this.audio.title = this.playList[nextIndex].title this.updateCurrentIndex(nextIndex) // 當(dāng)判斷到已經(jīng)到vuex的playList的邊界了,重新從storage中拿數(shù)據(jù)補(bǔ)充到playList if (nextIndex === this.playList.length - 1 || nextIndex === 0) { // 拿到只有當(dāng)前音頻前后最多5條數(shù)據(jù)的列表 const newList = getPlayList(this.playList[nextIndex].courseId, this.playList[nextIndex].audioId) // 當(dāng)前音頻在這5條數(shù)據(jù)中的索引 const index = newList.findIndex(item => item.audioId === this.playList[nextIndex].audioId) // 更新到vuex this.updateCurrentIndex(index) this.updatePlayList(newList) } } }
這里的getPlayList
方法是上面講過(guò)的,本來(lái)是從api中直接獲取的,為了避免每次都從api直接獲取,所以需要改一下,先讀storage,若無(wú)則從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 { // 沒(méi)有命中緩存,則從api中獲取 const list = await loadPlayList(courseId) wx.setStorage({ key: 'playList', data: list }) return subPlayList(list, currentAudioId) } }
播放上一首也是同理,就不贅述了。
PS: 將vuex中的數(shù)據(jù)精簡(jiǎn)后,我所做的小程序在播放音頻時(shí)刷其他頁(yè)面已經(jīng)非常流暢啦,效果非常好。
六、動(dòng)畫(huà)優(yōu)化
這個(gè)問(wèn)題在mpvue開(kāi)發(fā)音頻類小程序踩坑和建議已經(jīng)講過(guò)了,感興趣的可以移步看一眼,這里只寫(xiě)下概述:
如果要使用動(dòng)畫(huà),盡量用css動(dòng)畫(huà)代替wx.createAnimation使用css動(dòng)畫(huà)時(shí)建議開(kāi)啟硬件加速最后
大致總結(jié)一下上面所講的幾個(gè)要點(diǎn):
- 開(kāi)發(fā)時(shí)打開(kāi)
Vue.config._mpTrace = true。
- 謹(jǐn)慎引入第三方庫(kù),權(quán)衡收益。
- 添加數(shù)據(jù)到data中時(shí)要克制,能精簡(jiǎn)盡量精簡(jiǎn)。
- 圖片記得要壓縮,圖片在顯示時(shí)才渲染。
- vuex保持?jǐn)?shù)據(jù)精簡(jiǎn),必要時(shí)可先存storage。
性能優(yōu)化是一個(gè)永不止步的話題,我也還在摸索,不足之處還請(qǐng)大家指點(diǎn)和分享。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue select組件的使用與禁用實(shí)現(xiàn)代碼
這篇文章主要介紹了vue--select組件的使用與禁用的實(shí)現(xiàn)代碼,非常不錯(cuò),具有參考借鑒價(jià)值 ,需要的朋友可以參考下2018-04-04vue.js中引入vuex儲(chǔ)存接口數(shù)據(jù)及調(diào)用的詳細(xì)流程
這篇文章主要給大家介紹了關(guān)于在vue.js中引入vuex儲(chǔ)存接口數(shù)據(jù)及調(diào)用的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-12-12Vue2.0+Vux搭建一個(gè)完整的移動(dòng)webApp項(xiàng)目的示例
這篇文章主要介紹了Vue2.0+Vux搭建一個(gè)完整的移動(dòng)webApp項(xiàng)目的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-03-03node+vue前后端分離實(shí)現(xiàn)登錄時(shí)使用圖片驗(yàn)證碼功能
這篇文章主要介紹了node+vue前后端分離實(shí)現(xiàn)登錄時(shí)使用圖片驗(yàn)證碼,記錄前端使用驗(yàn)證碼登錄的過(guò)程,后端用的是node.js,關(guān)鍵模塊是svg-captcha,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11Vue首屏加載過(guò)慢出現(xiàn)白屏的6種優(yōu)化方案匯總
vue項(xiàng)目打包上線后,首次打開(kāi)會(huì)發(fā)現(xiàn)加載很慢,出現(xiàn)白屏的問(wèn)題,下面這篇文章主要給大家介紹了關(guān)于Vue首屏加載過(guò)慢出現(xiàn)白屏的6種優(yōu)化方案,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02淺析vue-cli3配置webpack-bundle-analyzer插件【推薦】
小編最近為了優(yōu)化vue項(xiàng)目性能,需要使用webpack-bundle-analyzer插件來(lái)分析報(bào)文件,在網(wǎng)上沒(méi)有找到合適的,下面小編給大家寫(xiě)出來(lái)一個(gè)供大家參考2019-10-10