前置基本知识
vuedevtool功能简介
安装
安装过程就不再赘述,可以参考官方的文档 ,官网上也描述了devtools不生效的场景。
概览
左侧4个tab
- components(组件的详细信息)
- Timeline(记录了事件的触发时间)
- Routes(查看页面的路由,当你接手一个老的项目的话,这个是非常有用的,尤其是在路由是由后端拼接而成的场景)
- Vuex(查看Vuex的各种状态)
右侧点击点点点的设置
- Component names 设置组件名称的显示格式
- Editable props 是否允许编辑props
- Hightlight updates 当更新的时候是否高亮
全局配置(通过点击上方的GLobal settings)
从左到右依次是
- Theme(主题色)
- Menu Step Scrolling(没用过)
- Performance monitoring (性能监控)推荐开启,我们在下面的案例中会用到
- Update tracking(更新追踪)
- Debugging info(调试信息)
下面我们依次介绍下每个模块的功能
具体的功能和使用使用场景
- components
方便查询当前组件的props、data、computed和route的一些信息
当你调试的时候可以直接修改props,在开发阶段很有用
- Timeline
记录了每个时间点的鼠标事件(Mouse)、键盘事件(keyboard)
鼠标事件截图
键盘事件截图
组件加载的时间
- Routes
记录了你的页面的所有的路由,方便你在接手一个新的项目的时候快速了解项目
- Vuex或者Pinia
在这个tab页面,你可以不用通过console看到当前Vuex或者Pinia的信息,提升你的开发效率
chrome开发者工具 performance
打开F12,将我们的tab切换到Performance,我们接下来会用到这个页面。
performance官方的解释:使用性能洞察面板获取有关您网站性能的可操作和用例驱动的洞察。说简单点就是来帮助我们进行优化的。
基本用法
我只介绍下基本的怎么用。如果你想系统学习,请直接去观看官方的学习视频。点击这里直接查看官方文档
案例分享
背景
一天领导安排做一个页面的优化。有一个功能是自定义表单有一个流程配置了181个组件,在电脑上候渲染了7秒多,需要优化一下。
页面的构成是这样的
- 一个自定义表单,里面有各种各样的组件,如datetimepicker、input、select、table等
- 当页面进入的时候,调用后端的接口拿到配置,然后for循环渲染页面
思考
拿到这个需求,先理清思路,我们可以按照下方的步骤来做
- 先统计接口耗时,观察是否受到接口的影响。如果需要后端协助,及时找领导安排后端人员的排期,免得到时候乱了阵脚
- 用vueDevtool记录组件耗时,找出耗时的组件,逐个攻破
- 用performance分析,找出问题点
接口排除
通过network查看接口耗时大概在50ms,没必要优化
统计组件渲染时长
注意看:两个时间的间隔就是当前组件的渲染时间。
重点重点重点 从页面上可以看到关键的4188.8直接到5078.ms
说明有个组件耗时过长了,耗时大概在890ms。展开后发现是dateTimePicker
验证
- 将当前的表单配置复制一份出来,方便对比。
- 新的表单配置去除了3个dateTimePicker
- 用同一个手机进行验证
结果如下
原流程耗时9.7s 新流程耗时5.6s
说明我们的排除思路是有效的
从VueDevtool得出的解决方案
- 可以写一个和dateTimerPciker同样样式的组件,当点击的时候,渲染真正的dateTimePicker组件
- 如果时间充裕的话可以看下dateTimePicker的源码。clone一份,自己研究下组件为什么耗时这么久
- 同理我们可以继续找出耗时的组件并且统计出来,然后解决
根据performance排查
接口耗时50ms,那么时间就耗费在渲染dom上,测试后发现假如是181个组件的话,需要等待for循环结束后页面才渲染出来。
下方为performance截图的截图。在 performance 面板中,通过看火焰图分析 call stack 和执行耗时。火焰图中每一个方块的宽度代表执行耗时,方块叠加的高度代表调用栈的深度。
通过上方的截图可以发现两个问题
- 获取配置的接口在netWork中耗时50ms,在performance中看到xhrload中时间线比较长,耗费了1.76s,大胆的猜测应该是有更高优先级的任务在执行
- Run Microtasks(微任务)耗费了1.76s
针对上面的第一个问题先寻找原因:
查阅文档后发现XHR是宏任务,Fetch请求是微任务,我们用的是XHR(微任务比宏任务执行的时间要早,微任务总是在宏任务之前去执行)。
既然第一个问题解释的通了,我们优化的方向就变成如何缩短Microtasks的时间
首次尝试:时间分片
这个概念还是在react filber的时候学会的。
目前这个流程dom正常的渲染需要3000多ms,共计181个组件。
181个组件是否可以分成5份,40个组件展示的内容足够填充满用户的首屏了。
先渲染一部分组件,然后再慢慢渲染第二组、第三组、第四组直到渲染结束,缓解用户的等待焦虑。
下方是编码的思路
- 对接口返回值进行group分组,
- 先渲染第一部分,等第一部分渲染完毕再渲染第二部分再重复进行(在nextTick中使用setTimeout进行对变量的赋值,利用js的异步事件机制来控制渲染)
下面是改造后的performance截图
- 任务已经分成了多个,在一次循环后再渲染另外的一组数据
- 同时Xhr执行时间也降低了
别的思路尝试
我们注意到我红色圈出的这里特别长,代表我们的调用堆栈过长。
在新增的页面要使用vue的特性双向绑定,但是在详情页面只是展示,我们是否可以将绑定配置的对象变成Non-reactive data(避免vue的递归响应式)。
转为 Non-reactive data,主要有三种方法:
- 数据没有预先定义在 data 选项中,而是在组件实例 created 之后再动态定义 this.configList (没有事先进行依赖收集,不会递归响应式);
- 数据预先定义在 data 选项中,但是后续修改状态的时候,对象经过 Object.freeze 处理(让 Vue 忽略该对象的响应式处理);
- 数据定义在组件实例之外,以模块私有变量形式定义(这种方式要注意内存泄漏问题,Vue 不会在组件卸载的时候销毁状态);
我测试下来影响不大,可能我这个数据不是那种很深或者数据量比较大。 但是也为大家提供一个思路
扩展阅读
我们知道setTimeout是有4ms的延迟,如果做的好的话,会去除这个4ms的延迟
我们可以使用下方的技术来实现(React Fiber的实现方法)
- 使用 requestAnimationFrame 获取渲染某一帧的开始时间,进而计算出当前帧到期时间点;
- 使用 performance.now() 实现微秒级高精度时间戳,用于计算当前帧剩余时间;
- 使用 MessageChannel 零延迟宏任务实现任务调度,如使用 setTimeout() 则有一个最小的时间阈值,一般是 4ms;
// 当前帧到期时间点
let deadlineTime;
// 回调任务
let callback;
// 使用宏任务进行任务调度
const channel = new MessageChannel();
const port1 = channel.port1;
const port2 = channel.port2;
// 接收并执行宏任务
port2.onmessage = () => {
// 判断当前帧是否还有空闲,即返回的是剩下的时间
const timeRemaining = () => deadlineTime - performance.now();
const _timeRemain = timeRemaining();
// 有空闲时间 且 有回调任务
if (_timeRemain > 0 && callback) {
const deadline = {
timeRemaining,
didTimeout: _timeRemain < 0,
};
// 执行回调
callback(deadline);
}
};
window.requestIdleCallback = function (cb) {
requestAnimationFrame((rafTime) => {
// 结束时间点 = 开始时间点 + 一帧用时16.667ms
deadlineTime = rafTime + 16.667;
// 保存任务
callback = cb;
// 发送个宏任务
port1.postMessage(null);
});
};
总结
- 当拿到一个页面后,我们先查看netWork的瀑布图进行分析,如果你不太懂可以查看我的这个文章进行简单的学习juejin.cn/post/722555…
- 用可视化工具vueDevtool或者ReactDevTool进行分析,分析出耗性能的组件,然后带着目的去优化
- 用Performance分析执行耗时,分析哪个阶段我们可以进行优化
- 多学习学习vue和react的设计思路,在我们的开发过程中如果遇到了同样的问题,可以参考这些开源组件的思想解决
参考
有道技术博客 techblog.youdao.com/?p=3055