天天看点

你可能不知道的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

继续阅读