一、背景
随着涂鸦小程序应用的持续推广,用户和开发者数量越来越多,小程序使用体验也显得越来越必要,不能给用户树立一个小程序就是卡顿的代名词。在开发小程序时,应用的体积和性能会直接影响用户使用体验。过大的体积会耗费更长的加载时间,同时涂鸦 IDE 也限制了打包的大小,所以我们在开发涂鸦小程序的时候需要密切关注小程序的打包体积与性能,那么如何入手去优化一个 Ray 小程序呢?
二、问题
我们之前对外开放过一版照明幻彩灯带项目,用户反馈存在几个体验上的问题:
1、切换个别 tab 时,卡顿明显。
2、首次加载有时存在白屏时间较长的情况。
3、面板上灯带颜色与色盘颜色建议实时同步,当前为松手更新面板灯带颜色,这样的话只有松手时才能看到灯带颜色最终效果。
三、分析
1、切换个别 tab 时卡顿
1.1、 经分析主要集中在情景 tab 切换时, FPS 值为12 FPS (推荐 >= 35FPS), MT 值为400+ (推荐 <= 50)。
1.2、当快速操作面板时 MT 值会持续变大,说明传输的数据量过多。
2、加载时间较长
2.1、通过比对打包下的体积,发现打包体积在 1.654 MB,体积较大,在网络较差或机子性能较差的情况下加载较慢,考虑进行打包体积优化。
- 色盘同步更新
3.1、由于滑动面板时数据通过需要经过逻辑层再传递到灯带的视图层,同时滑动时更新频率很高,所以之前未按照实时更新实现。
四、优化
1、定位体积问题
1.1、包体积可视化
1.1.1、作用:
1.1.1.1、可排查哪些包占据过多的空间,看是否可以移除或替换
1.1.1.2、查看哪些包未在项目中引用却被打包进了dist/tuya
1.1.2、具体操作:
1.1.2.1、项目目录运行npx ray start --target=tuya -a 会在 dist/tuya下生成 create-pages-metafile.json 、create-render-scripts-metafile 、 create-safe-scripts-metafile 三个文件
1.1.2.2、导入 https://esbuild.github.io/analyze/ 中即可查看具体代码占用的体积
1.1.2.3、导入文件生成图后进行分析,如下图(unassigned 表示未引入的代码,可忽略)
lodash 未按需引入,存在重复引入的问题, lottie.js 占据了大量代码空间
lamp-hue-picker 和 lamp-rhythm-circle 以及 lamp-circle-wheel 等组件未使用,却被打入(组件使用 rjs/sjs 开发模式,不支持 tree-shaking),需要按需引入
lamp-step-slider 和 lamp-vertical-touch-slider 以及 lamp-vertical-percent-slider等组件未使用,却被打入(组件使用 rjs/sjs 开发模式,不支持 tree-shaking),需要按需引入
1.2、 图片压缩或上云
1.2.1、一般从 figma 导出的图都是未经过压缩的,可以使用 https://tinypng.com/ 在线进行压缩
原始图片体积:
压缩后图片体积:
=> 图片整体体积可减少 70%+
1.2.2、如果压缩后图片还是比较大,可以使用 IDE 中的 CDN 功能进行图片上云,减少本地空间的占用 (可根据项目自行判断哪些上云)
1.2.2.1、上传 CDN 步骤:
- 在项目根目录创建目录 cdn
- project.tuya.json 中配置 "publicRoot": "cdn/"
- 点击 ide 中的 CDN 上传, 会在 cdn 目录中生成 cdnImage.json 目录
- 封装 utils/getCdnImgUrl 方法
Code: Select all
import { getCdnUrl } from '@ray-js/ray';
import cdnMap from '../../cdn/cdnImage.json';
const getCdnImgUrl = imgName => {
const res = getCdnUrl(imgName, cdnMap);
return res;
};
export default getCdnImgUrl;
- 项目中使用
2、定位性能问题
Code: Select all
const icon = getCdnImgUrl(local_music_${iconName}.png;
2.1、vConsole 性能工具
2.1.1、使用小程序 vConsole 性能工具进行监测
通过 vConsole 判断性能瓶颈主要在 多个 Lottie 同时加载运行,由于每个 Lottie 都是一个单独的 Canvas,所以很快达到了页面的性能瓶颈
色盘拖动色块时,视图层和逻辑层频繁交互更新数据,导致性能下架
2.1.2、开启方式:
长按小程序右上角 X,找到 打开调试 按钮,启用后可以看到 vConsole,点击打开后,切换到 Perf tab上,找到 Open FPS monitor 选项,启用即可(点击启用的性能窗口可切换性能指标)
2.1.3、使用方式:
切换到对应的指标后,进行有问题的操作,查看具体是什么原因导致
MT 对应 数据量的传输,数据越大体验越差
FPS 对应 线程的执行负载程度,当负载过大或阻塞会导致 FPS 值变小
2.1.4、优化:
针对本项目,分析代码可知:
- 情景 tab 下有多个 Lottie 动画同时会请求多个 Lottie 动画 json 文件数据,而小程序中每个 Lottie 都是一个单独的 Canvas,一个页面中同时存在多个 Lottie 并且传输大量数据,页面性能跟不上,导致卡顿。
- 经量化分析( viewtopic.php?p=10835&hilit=lottie#p10835 ),当前项目情景页面使用了超过8个 Lottie 情景动画,远超过了最佳推荐的个数,故而使用 gif 替换 Lottie 动画。
- 为了优化逻辑层和视图层频繁的数据交互,小程序团队开发了 事件通道(eventChannel) 能力来实现 RJS 与 SJS 的传输性能,具体使用方法见 eventChannel 具体用法文档。
2.2、useDebugPerf hooks
2.2.1、使用 useDebugPerf 查看短时内渲染次数,优化高频/无效渲染
2.2.2、在组件中引入 useDebugPerf ,检测到了首页中的【灯带】组件存在高频渲染问题,使用 if 大法 判断空数据,直接 return,防止多次无效渲染
2.2.3、使用效果:从图中可以看出多个组件存在重复渲染,并且渲染原因为 onChange 属性函数未做 useCallback。
Code: Select all
import { useEffect, useRef } from 'react';
const isDev = process.env.NODE_ENV === 'development';
/**
* @param component 组件
* @param props 属性 可选
* @param maxCount 超过最大渲染次数 可选 默认6次
* @param time 时间段(ms) 可选 默认 500ms
*/
const useDebugPerf = (component, props, maxCount = 6, time = 500) => {
const count = useRef(0);
const timer = useRef(null);
const prevProps = useRef({});
const componentName = component.displayName || component.name || 'Unknown Component';
useEffect(() => {
if (!isDev) {
return;
}
count.current++;
if (count.current >= maxCount) {
if (prevProps.current) {
const allKeys = Object.keys({ ...prevProps.current, ...props });
const changedProps = {};
allKeys.forEach(key => {
if (!Object.is(prevProps.current[key], props[key])) {
changedProps[key] = {
from: prevProps.current[key],
to: props[key],
};
}
});
if (Object.keys(changedProps).length) {
console.log(`%c[useDebugPerf =>] ${componentName}`, 'color: #75fbfd;', changedProps);
}
}
const timeStr = (time / 1000).toFixed(1);
console.log(
`%c[useDebugPerf =>] %c 警告: ${timeStr}s内渲染次数超过:${maxCount}次; %c 组件名称: ${componentName}`,
'color: #75fbfd;',
'color: #ef906e;',
'color: #33b9ff;'
);
}
prevProps.current = props;
if (!timer.current) {
timer.current = setTimeout(() => {
count.current = 0;
clearTimeout(timer.current);
timer.current = null;
}, time);
}
return () => {
if (timer.current) {
clearTimeout(timer.current);
timer.current = null;
}
};
});
};
export default useDebugPerf;
四、成果
1、体积
1.654 MB => 0.605 MB [ 体积减小 63%]
2、性能
2.1、FPS
- 13+ => 60+ [ 提升 361%] 2.2、MT
- 220+ => 43+ [ 降低 80%+]
五、总结
当开发小程序遇到体验问题时,可以从两个方面入手, 一个是体积, 一个是性能。
1、体积
可通过 ide 功能中的设置=>基本信息中看到 (如果优化后体积大小没有变化可点击“真机预览” 上传一次),然后根据上面描述的方法逐项优化。
2、性能
【性能检测】
开启 vConsole 和 useDebugPerf hooks 进行性能检测。
【性能判断】:
通过上面的工具判断是性能瓶颈是更新频率过高(useDebugPerf)还是线程过载(FPS)或是逻辑层与视图层传递数据过多过快(MT)。
【高频优化】:
当有高频操作且页面状态需要实时更新时,建议使用 rjs 或 sjs 能力。
如果存在跨组件高频实时操作,建议使用 eventChannel 能力,注意 eventChannel 只能用于rjs 或 sjs 之间相互间的数据传输(比如sjs => sjs/rjs 或 rjs => rjs/sjs)
【元素拆分】:
如果页面存在过多的图片或比较消耗性能的元素,建议通过交互进行拆分,防止在一个页面中展示或保存过多内容。
【能力替换】:
动画:尽量避免在页面中使用多个 Lottie 组件,如果需要存在多个动画可以使用 gif 替换,如果 gif 也过多的话,需要交互拆分。