• 【小程序】爆肝 3 天总结的微信小程序优化指南(收藏夹吃灰吧!)


    前言

    大家好,我是HoMeTown,最近要做一个小程序的项目,项目启动之前,回顾自己之前做过的小程序,感觉做的还是不够好,最近学习了一下小程序优化方案,这块总结一份个人笔记,以便参考,同时分享给大家,共勉。

    关于微信小程序

    微信小程序上线至今已经大约已经有5年的时间,5年的时间,小程序的开发能力经过微信团队的不断努力优化,已经日益完善,不再像之前一样,翻翻文档就能完成一个简易的项目了,特别是性能优化方面,会优化与不会优化更是对产品的效果有着决定性的影响。

    哪些部分需要优化?

    • 首页白屏现象
    • Loading加载时间过长,页面好久不显示
    • 点击页面链接,页面跳转迟钝
    • 按钮单击无响应
    • 长内容列表,内容越多,越卡顿

    有了优化的方向,就可以根据问题逐一诊断了

    小程序的启动流程?

    运行载体

    • iOS、Mac
    • Android、PC
    • 微信开发者工具

    环境准备

    • 小程序运行进程及运行环境准备
    • 代码包下载、校验及初始化
    • 视图层系统组件、WebView容器和原生组件的初始化
    • 原生组件的初始化
    • 逻辑层JS引擎初始化及域创建

    代码注入

    • 框架及第三方基础代码的初始化
      • 小程序基础库注入
      • 扩展库注入
      • 插件、自定义组件注入
    • 开发者代码注入
      • 逻辑层代码「这里会派发App.onLaunch还有App.onShow这些事件」
      • 视图层代码

    首屏渲染

    • 逻辑层页面初始化,这个时间点是initDataSendTime,会派发Page.onLoad事件
    • 视图层时间点走到viewLayerReaderStartTime,会派发Page.onShow事件
    • 开发者代码从后端拉取,准备data数据
    • 页面渲染
    • 视图层时间点走到viewLayerReaderEndTime,会派发Page.onReady事件,意味着首屏渲染完成

    启动方式

    冷启动

    小程序在用户设备上第一次打开或者是销毁之后再打开,或者是30分钟以后

    热启动

    热启动是相对于冷启动而言的,热启动是小程序启动的一种优化机制,小程序进入后台30分钟以内再次进到前台,可以直接从后台状态然后回复到前台,所以,在这种情况下,刚刚那个代码注入首屏渲染等基础工作就不会再执行了,设置App.onLaunchPage.onLoad等这些一次性的生命周期,也不会有了。

    结论
    由此,可以得出,要做小程序的性能优化,主要是做冷启动这块的优化以及运行时渲染性能的一个优化,小程序冷启动流程里涉及到一些程序和生命周期。

    生命周期

    在小程序中App和Page都有他们各自的生命周期函数。

    App

    • onLaunch,监听小程序初始化的事件
    • onShow 监听小程序启动或切前台的事件
    • onHide 监听小程序切后台的时间

    Page

    • onLoad 监听页面加载
    • onShow 监听页面显示
    • onReady 监听页面初次渲染完成
    • onHide 监听页面隐藏
    • onUnload 监听页面卸载

    优化技巧

    使用骨架屏

    骨架屏的目的是为了减缓用户等待的情绪

    使用

    如图,在小程序开发者工具中,点击这里,生成骨架屏代码
    在这里插入图片描述

    然后他会生成两个文件:index.skeleton.wxml & index.skeleton.wxss

    分别在index.wxml & index.wxss引入

    在这里插入图片描述

    总结

    • 在data数据对象中默认设置loading等于true。
    • 不要直接修改生成的骨架屏的一个代码。通过配置去修改。
    • **不要过度去使用骨架屏。一般只给主页去添加骨架屏效果 **

    优化长列表

    优化长列表,使用recycle-view & recycle-item 虚拟DOM组件,在渲染的时候需要知道每个循环单元的一个高度,这是消费代码需要传递给组件的,在recycle-view组件里面,用户滑动的是数据而不是组件本身。

    使用
    首先初始化package.json

    // npm初始化package.json文件
    npm init -y
    
    • 1
    • 2

    安装

    // 安装 miniprogram-recycle-view插件
    npm install --save miniprogram-recycle-view
    
    • 1
    • 2

    构建依赖
    在这里插入图片描述

    生成miniprogram_npm目录

    在这里插入图片描述

    组件内引用
    在这里插入图片描述

    代码中使用

    list.wxml

    
      长列表前面的内容
      
        
            
          {{item.idx+1}}. {{item.title}}
        
      
      长列表后面的内容
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    list.js

    const createRecycleContext = require('miniprogram-recycle-view')
    Page({
      onReady: function () {
        var ctx = createRecycleContext({
          id: 'recycleId',
          dataKey: 'recycleList',
          page: this,
          itemSize: this.itemSizeFunc
        })
        ctx.append([{
          image_url: 'https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/09883c4389f642829c96b6216eebea85~tplv-k3u1fbpfcp-watermark.image?',
          idx: 1,
          title: '你好'
        }])
        // ctx.update(beginIndex, list)
        // ctx.destroy()
      },
    
      onScroll() {
        console.log('scroll')
      },
      itemSizeFunc: function (item, idx) {
        console.log(item)
        return {
          width: 375,
          height: 100
        }
      }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    总结

    代码写起来不是那么舒服,组件使用不友好,但是对性能确实还不错。

    使用页面容器

    有时候我们在页面上会弹出一些特定的半屏窗口,例如登录窗口。这时候如果用户在iOS设备上使用了左滑手势或者是在Android设备上单击了物理返回键,会造成页面跳转到上一个页面,这在大多数情况下它并不是用户的真实本意,用户可能只是想将当前弹出的半屏的假页面给它关掉。

    page-container页面容器,是不需要引入便可以使用。

    使用

     
        ...
    
    
    • 1
    • 2
    • 3

    总结

    这个组件其实就是防止用户的左滑或者按键返回误操作,导致整个页面回退的问题。

    优化动画效果

    实现动画的方式:

    • 是使用Animation对象,实现CSS动画
    • 使用页面或组件对象,拥有的animate方法实现的关键帧动画
    • 使用滚动事件驱动的响应式动画
    • 通过WXS脚本,实现了一个样式动画
    • 通过第三方库实现animate.css

    代码按需注入

    代码懒加载

    使用

    // app.json
    "lazyCodeLoading": "requiredComponents"
    
    • 1
    • 2

    出现以下提示,代表成功

    在这里插入图片描述

    静态初始化渲染缓存

    第一次页面运行时,由微信客户端负责将页面在本地的某个区域缓存起来,下次在真正的页面未加载完成前,先展示这个缓存过的页面。

    使用

    // xxxx.json
    "initialRenderingCache": "static"
    
    • 1
    • 2

    动态初始化渲染缓存

    与静态初始化渲染缓存,动态初始化渲染缓存可以设置动态缓存的数据,放在onReady生命周期中

    使用

    // xxx.json
    "initialRenderingCache": "dynamic"
    
    • 1
    • 2
    // xxx.js
    // 设置动态缓存数据
    this.setInitialRenderingCache({
        customersList: customerList
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果出现以下的信息,不用在意。

    在这里插入图片描述

    在手机里会有一条成功的信息:

    update view with init data
    
    • 1

    分包

    小程序包大小规定,单个代码包不超过2MB,总包大小不超过20MB,所以到我们业务庞大,代码量大导致项目大小上升,就必须用分包的形式,解决这个问题。

    使用

    // app.json
    {
        "pages": [
            // "pages/user/index"
        ],
        "subpackages": [
            {
                "root": "user",
                "pages": [
                    "pages/user/index"
                ]
            }
        ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    独立分包

    使用

    {
        "pages": [
            // "pages/user/index"
            // "pages/goods/index"
        ],
        "subpackages": [
            {
                "root": "user",
                "pages": [
                    "pages/user/index"
                ]
            },
            {
                "root": "goods",
                "pages": [
                    "pages/goods/index"
                ],
                "independent": true // 独立分包关键配置
            }
        ]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    // pages/goods/index.js
    const app = getApp({allowDefault: true}) // 独立分包中getApp为空,这里设置默认值
    
    • 1
    • 2

    独立分包之后访问独立分包页面,主包和组件是不会被加载的,所以要分实际业务场景,进行分包

    分包预加载

    使用

    "preloadRule": {
    // 分包root路径+后面的路径
        "goods/pages/detail/index": {
            "network": "all",
            "packages": [
                "__APP__"
            ]
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    看到这个提示,表示成功

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZW8RQRvp-1667270755277)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/849fa956036d4e4c815be5af51cc1f60~tplv-k3u1fbpfcp-watermark.image?)]

    总结

    • tabbar最好自己实现
    • 所有在根目录下的文件,如果没有分包,都会打包进主包,所以一般情况下,只放一个首页进主包
    • 分包配置需要指定一个root目录,该目录下所有的文件都会自动被分割到这个分包里
    • 对于相对独立的页面(例如分享页),可以进行独立分包,独立分包的页面会有「返回首页的按钮」,一般队里分包里,都需要设置分包预加载

    使用占位组件

    使用占位组件延迟加载自定义组件

    使用
    创建一个名称为index_addons的一个组件分包

    // app.json
    "subpackages": [
        {
            "root": "index_addons",
            "pages": []
        }
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    修改主页配置

    {
        "componentPlaceholder": {
            "stopwatch": "view" // 欲用作占位组件
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在stopwatch中添加代码

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WM3vwA4f-1667270755277)(https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/495e86c0eae0479989a362810da61860~tplv-k3u1fbpfcp-watermark.image?)]

    调试

    • 设置为2g网
    • 本地设置里,设置懒注入占位组件调试

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rfbrh0m8-1667270755278)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5b8807674da347d4b71909ec45098392~tplv-k3u1fbpfcp-watermark.image?)]

    如果没有这个选项,请确认是否有配置 "lazyCodeLoading": [requiredComponents]

    分包异步化

    • require
    • require.async
      注意,只支持相对路径

    使用

    const { default: getNavList } = await require.async("../../../index_addons/compopnents/get_nav_list.js")
    
    • 1

    视图代码优化技巧

    动态列表渲染里优化wx:key的使用

    确保wx:key的值是唯一值

    如果列表元素是单一的基本数据类型,并且是唯一的,这时候我们可以直接写成this,这里这个this,就代表当前数据列表里面的数据元素

    
    
    • 1

    如果列表元素是静态的,只渲染一次,那么可以直接用index

    
    
    • 1

    如果数据是从后台获取的,那么wx:key需要有一个唯一值

    
    
    • 1
    绑定视图事件

    使用catch代替bind,减少dataset的数据运输量,因为我们在大多数情况下,我们的事件不需要冒泡,所以bind很浪费性能,catch是不会冒泡的,事件传递某些信息的时候,需要什么传什么,不要一刀切,图省事,传递一个很大的对象,如果需要整个对象,建议使用index进行传递

    view detail
    
    • 1
    防抖&节流
    节流

    节流顾名思义就是控制某段JS代码的执行频率

    function throttle(method, wait = 50) {
        let previous = 0
        return function(...agrs) {
            let context = this
            let now = new Date().getTime()
            if(now - previouts > wait) {
                method.apply(context, args)
                previous = now
            } else {
                console.log("节流少许")
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    防抖

    顾名思义就是防止抖动,避免把一次时间当做多次处理,敲击键盘就是一个经常都会遇到的防抖操作场景

    function debounce(func, wait = 50) {
        let timer = null
        return function (...args) {
            const contest = this
            if(timer) {
                clearTimeout(timer)
                console.log("防抖少许")
            }
            timer = setTimeout(() => {
                func.call(context, ...args)
            }, wait)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    组件中使用
    const {default:debounce} = require(../../../library/optimus/debounce.js)
    const {default:throttle} = require(../../../library/optimus/throttle.js)
    
    onScrolllToLower: throttle(function(e) {
        ...
    })
    
    onTapCustomerItem: debounce(function(e) {
        ..
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    尽量少的使用重渲染和wxml标签

    官方建议:

    总页面节点数少于1000个,节点树深度层级少于30层,子节点数不大于60个

    所以,在开发过程中,能用,就不用,能用文本,就不用,对于循环列表中,能用就不用

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8xpMF0D7-1667270755278)(https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ef954f7253514eb0bc8dac0e34a8c7a2~tplv-k3u1fbpfcp-watermark.image?)]

    WXSS优化技巧

    给滚动组件开启惯性滚动
    -webkit-overflow-scrolling: touch;;
    
    • 1
    使用hover-class实现按钮的单击态
    <button class="submit-btn" hover-class="submit-btn__hover">button>
    
    • 1
    .submit-btn {
        ...
    }
    .submit-btn__hover {
        ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    使用gulp工具删除无用的wxss样式代码

    安装

    npm install gulp -g
    npm install --save-dev gulp gulp-cleanwxss
    
    • 1
    • 2

    配置
    /tools/gulpfile.js

    const gulp = require("gulp")
    const cleanwxss = require("gulp-cleanwxss")
    
    // 处理父目录下的样式文件,输出到当前目录下的dist
    gulp.task("default", (done) => {
        group.src("../minniprogram/index/pages/*/*.wxss")
            .pipe(cleanwxss({ log: true }))
            .pipe(gulp.dest("./dist"))
         done()
    })
    
    // 处理分包下的样式文件
    gulp.task("goods", (done) => {
        group.src("../minniprogram/goods/pages/*/*.wxss")
            .pipe(cleanwxss({ log: true }))
            .pipe(gulp.dest("./dist"))
         done()
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    执行

    gulp default
    gulp goods
    
    • 1
    • 2

    CV

    将生成出来的样式文件复制到对应目录即可

    UI交互优化技巧

    使用padding扩大可点击区域大小
    .submit-btn {
        ...
        padding: 10px;
    }
    
    • 1
    • 2
    • 3
    • 4
    使用伪元素扩大可点击区域大小
    .submit-btn {
        position: relative;
        ...
    }
    .submit-btn::after {
        content: '',
        position: absolute;
        top: -20px;
        right: -20px;
        bottom: -20px;
        left: -20px;
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    脚本优化技巧

    清理定时器
    clearTimeout(timer1)
    
    • 1
    使用wx.onXxx全局绑定,有一个监听,必须有一个反监听
    // 及时释放本页范围内添加的全局监听器
    onLoad() {
        wx.onThemeChange(this.themeChangeHandler)
    }
    onUnload() {
        wx.offThemeChange(this.themeChangeHandler)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    模拟器测试需要在app.json中添加

    "darkMode": "true"
    
    • 1
    小心使用全局对象,适当时机清理

    setData调用优化

    不要多次分开调用setData,尽量合并
    // bad
    if(this.data.gender === '1') {
        this.setData({
            genderName: '男'
        })
    } else if(this.data.gender === '0') {
        this.setData({
            genderName: '女'
        })
    } else {
        this.setData({
            genderName: '未知'
        })
    }
    
    // good
    let genderName = '未知'
    if(this.data.gender === '1') genderName = '男'
    if(this.data.gender === '0') genderName = '女'
    this.setData({
        genderName,
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    不准备渲染的数据,不要放在data对象中
    Page({
        allList: [],
        pageNum: 1,
        pageSize: 10,
        data: {
            list: []
        }
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    通过index局部更新列表数据
    this.setData({
        [this.data.customerList[index].title]: item.title+='---'
    })
    
    • 1
    • 2
    • 3

    网络请求优化技巧

    减少不必要的网络请求,使用本地缓存数据

    技术点:Storage

    优化网络请求参数,提高网络请求效率
    enableCache: true,
    enableHttp2: true,
    enableQuic: true
    
    • 1
    • 2
    • 3
    优化网络请求的并发数,分优先级
    npm i priority-async-queue
    
    • 1

    request.js

    const PriorityAsyncQueue = require('priority-async-queue')
    const queue = new PriorityAsyncQueue(10) // default 10
    
    const low = "low", normal = "normal", mid = "mid", high = "high", "urgent" = "urgent"
    
    export const priority = { low, normal, mid, high, urgent }
    
    ....
    
    return new Promise((resolve, reject) => {
        queue.addTask({ priority }. () => {
            wx.request(Object.assgin(args,{
                success: resolve,
                fail: reject
            })
        })
    })
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    图片优化技巧

    减少图片的请求次数
    压缩图片大小
    尽可能使用CDN图片或图片链接
    尽可能使用webp格式图片

    完结

    小程序的优化大致就是从这些方面入手了,觉得有用的同学,点个赞,收个藏吧!

    往期回顾

  • 相关阅读:
    揭开ChatGPT面纱(3):使用OpenAI进行文本情感分析(embeddings接口)
    FreeRTOS 简单内核实现8 时间片轮询
    数据分析是大数据最热门的投资赛道
    k8s调度之污点和容忍
    跨域请求处理之配置代理模式
    2020最新Java常见面试题及答案
    Java程序设计实验5 | Java API应用
    【SpringBoot】YAML 配置文件
    组成目标货币的最少张数
    美创科技入选第二届安徽省网络和数据安全应急技术支撑单位
  • 原文地址:https://blog.csdn.net/isHoMeTown/article/details/127629587