最近一直在折騰
mpvue
寫的微信小程式的性能優化,分享下實戰的過程。
先上個優化前後的圖:
可以看到打包後的代碼量從
813KB
減少到
387KB
,Audits體驗評分從
B
到
A
,效果還是比較明顯的。其實這個名額說明不了什麼,而且輕易就可以做到,更重要的是優化 小程式運作過程中的卡頓感,請耐心往下看。
正常優化
正常的Web端優化方法在小程式中也是适用的,而且不可忽視。
一、壓縮圖檔
這一步最簡單,但是容易被忽視。在tiny上線上壓縮,然後下載下傳替換即可。
我這項目的壓縮率高達
72%
,可以說打包後的代碼從
813KB
降到
387KB
大部分都是歸功于壓縮圖檔了。
二、移除無用的庫
我之前在項目中使用了Vant Weapp,在
static
目錄下引入了整個庫,但實際上我隻使用了
button
,
field
,
dialog
等幾個元件,實在是沒必要。
是以幹脆移除掉了,微信小程式自身提供的
button
,
wx.showModal
等一些元件基本可以滿足需求,自己手寫一下樣式也不用花什麼時間。
在這裡建議大家,在微信小程式中,盡量避免使用過多的依賴庫。
不要貪圖友善而引入一些比較大的庫,小程式不同于
Web
,限制比較多,能自己寫一下就盡量自己寫一下吧。
小程式的優化
咱們首先得看一下官方優化建議,大多是圍繞這個建議去做。
一、開啟Vue.config._mpTrace = true
這個是 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更新的資料量。如圖:
如果資料更新量很大,會明顯感覺小程式運作卡頓,反之就流暢。是以我們可以根據這個名額,逐漸找出性能瓶頸并解決掉。
二、精簡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渲染很多圖檔時的卡頓
- 想請教一下小程式swiper元件的問題
在我使用時,由于需求原因,動态删掉swiper-item的思路不可行(手滑時會造成抖動)。是以隻能作罷。但仍然可以優化一下:
- 将未顯示的
中的圖檔用swiper-item
隐藏到,當判斷到current時才顯示,防止大量圖檔的渲染導緻的性能問題。v-if
四、vuex使用注意事項
我之前寫過的一篇mpvue開發音頻類小程式踩坑和建議裡面有講如何在小程式中使用
vuex
。但遇到了個比較嚴重的性能問題。
1. 問題描述
我開發的是一個音頻類的小程式,是以需要将播放清單
playList
,目前索引
currentIndex
和目前時長
currentTime
放進
state.js
中:
const state = {
currentIndex: 0, // playList目前索引
currentTime: 0, // 目前播放的進度
playList: [], // {title: '', url: '', singer: ''}
}
複制代碼
每次使用者點選播放音頻時,都會先加載音頻的播放清單
playList
,然後播放時更新目前時長
currentTime
,發現有時候播音頻時整個小程式非常卡頓。
注意到,音頻需每秒就得更新一次,即每秒就做一次
currentTime
操作,稍微有些卡頓是可以了解的。但我發現是播放清單資料比較多時會特别卡,比如playList的長度是100條以上時。
setData
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
的寫法能改善這個問題,雖然使用時命名空間造成一定的麻煩。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)
在這5個資料的索引是 ,沒有currentAudio
,有4個上一首
下一首
- 目前音頻是清單中的第2條(索引是1),截取前5個:
,此時playList.splice(0, 5)
在這5個資料的索引是currentAudio
,有1個1
,3個上一首
下一首
- 目前音頻是清單中的第3條(索引是2),截取前5個:
,此時playList.splice(0, 5)
在這5個資料的索引是currentAudio
,有2個2
,2個上一首
下一首
- 目前音頻是清單中的第4條(索引是3),截取第1到6個:
,此時playList.splice(1, 5)
在這5個資料的索引是currentAudio
,有2個2
,2個上一首
下一首
- 目前音頻是清單中的第5條(索引是4),截取第2到7個:
,此時playList.splice(2, 5)
在這5個資料的索引是currentAudio
,有2個2
,2個上一首
下一首
- ...
- 目前音頻是清單中的第9條(索引是
),截取後5個:8
,此時playList.splice(4, 5)
在這5個資料的索引是currentAudio
,有3個3
,1個上一首
下一首
- 目前音頻是清單中的最後1條(索引是
),截取後的5個:9
,此時playList.splice(4, 5)
在這5個資料的索引是currentAudio
,有4個4
,沒有上一首
下一首
有點啰嗦,感興趣的同學仔細琢磨下,無論目前音頻在哪,都始終保證了拿到目前音頻前後的最多5條資料。
接下來就是維護播放上一首或下一首時保證目前vuex中的
playList
始終是包含目前音頻的前後2條。
播放下一首
function playNextAudio() {
const nextIndex = this.currentIndex +
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 - || nextIndex === ) {
// 拿到隻有目前音頻前後最多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開發音頻類小程式踩坑和建議已經講過了,感興趣的可以移步看一眼,這裡隻寫下概述:
- 如果要使用動畫,盡量用css動畫代替wx.createAnimation
- 使用css動畫時建議開啟硬體加速
最後
大緻總結一下上面所講的幾個要點:
- 開發時打開
。Vue.config._mpTrace = true
- 謹慎引入第三方庫,權衡收益。
- 添加資料到data中時要克制,能精簡盡量精簡。
- 圖檔記得要壓縮,圖檔在顯示時才渲染。
- vuex保持資料精簡,必要時可先存storage。
性能優化是一個永不止步的話題,我也還在摸索,不足之處還請大家指點和分享。
歡迎關注,會持續分享前端實戰中遇到的一些問題和解決辦法。
轉載于:https://juejin.im/post/5ca5a6236fb9a05e7207eea9