问题前后效果对比
问题如下,点击切换公司时,要延迟3s左右才请求接口获取数据:
修复后的效果:
项目背景
在pc某管理系统项目中,需要对客户的组织架构信息进行列举,并点击可查看对应的客户人员信息【如下图所示】
发现问题
在官网添加了该组件,并设置了render
属性,给每一个公司都设置前缀icon,结果在预览时发现点击切换公司时,会有3-5s的一个卡顿,然后再请求接口,总数据才460条,正常情况下是不会出现这么大的延迟响应。
排查问题
经过反复的测试之后(1 hours later…),发现是组件的 :render="renderContent
这个属性导致的卡顿,取消该属性后再切换公司基本就在20ms内。
思路:页面卡顿的很大一部分原因是因为线程阻塞导致的,这时我们就需要排查哪里有长任务
通过分析performance面板的火焰图发现有一条remove(右边显示这些方法来自 vue.runtime.esm.js)方法,这个方法是 Vue 内部递归响应式的方法
为什么这些方法会长时间占用主线程呢?在 Vue 性能优化中有一条:不要将复杂对象丢到 data 里面,否则Vue 会深度遍历对象中的属性添加 getter、setter(即使这些数据不需要用于视图渲染),进而导致性能问题。
解决问题
将data里定义的响应式变量,变为为Non-reactive data
(避免 Vue 递归响应式)
转为 Non-reactive data,主要有三种方法:
- 数据没有预先定义在 data 选项中,而是在组件实例 created 之后再动态定义 this.deptArr (没有事先进行依赖收集,不会递归响应式);
- 数据预先定义在 data 选项中,但是后续修改状态的时候,对象经过 Object.freeze 处理(让 Vue 忽略该对象的响应式处理,测试无效);
- 数据定义在组件实例之外,以模块私有变量形式定义(这种方式要注意内存泄漏问题,Vue 不会在组件卸载的时候销毁状态,测试该方法定义的变量不能在template中使用);
这里我们使用第一种方法,将 deptArr改成 Non-reactive data 试一下:
created() {
this.deptArr = [] // 此变量不在data的return定义,直接在created这里赋值
this.getDeptTree();
},
methods:{
getDeptTree() {
this.$api.sysset.getCustomTree({
name: this.customObj.customName,
userId: this.customObj.userId
}, res => {
this.deptArr = res.data;
})
},
}
然后就发现页面回复了正常,就只是在初始化的时候,数据渲染比较慢,后面切换不同公司都很流畅。后续可能会更新其他解决方法,这个只是一个比较快速的临时方案,如果有大量数据,还是考虑虚拟列表、懒加载等。
有什么问题欢迎评论讨论!