天天看點

你可能不知道的mpvue性能優化技巧

最近一直在折騰

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

    中的圖檔用

    v-if

    隐藏到,當判斷到current時才顯示,防止大量圖檔的渲染導緻的性能問題。

四、vuex使用注意事項

我之前寫過的一篇mpvue開發音頻類小程式踩坑和建議裡面有講如何在小程式中使用

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

    的寫法能改善這個問題,雖然使用時命名空間造成一定的麻煩。vuex傳送門

一般情況下,推薦使用後者。我在項目中嘗試使用了前者,同樣能達到很好的效果,請繼續看下面的分享。

五、善用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個資料的索引是 ,沒有

    上一首

    ,有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 + 
    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

繼續閱讀