德胜云资讯,添加一些关于程序相关的内容,仅供大家学习交流(https://www.wxclwl.com)
日期:2023/03/26 04:16作者:何美玲人气:
下栽の地止:https://www.itwangzi.cn/4372.html
前段时间公司有一个比较重要的模块从 vue2 升级到 vue3,升级后发现 element-plus table 的性能相比 vue2 版本下降非常严重。
自定义列全部勾选的场景下(20 行 x 180 列),列表中的开关切换,耗时从原先的 400-500 毫秒下降到 7-8 秒,严重影响用户体验,经过较长时间的性能测试、debug,找到了几处比较核心的优化点。
先来看一下 20 行 x 180 列场景下各个优化点的性能测试数据,为排除偶然性,每个场景都会测 3 次。
大致优化内容如下
首先初始化一个 vue3 项目,引入 element-plus,并使用 el-table 实现一个 20 行 * 180 列表格。
1-table-base.png
核心 table 代码代码如下,完整代码参见:table-base | table-performance-demo[1]
渲染耗时计算逻辑如下,利用 script 阻塞,来计算渲染耗时
/*
当前显示:{{ `${rowCount}行${customColCount + 2}列` }}, 显示/隐藏 table:
/ 1000}s`);
}, 0);
}, 200);
};
table 渲染、switch 切换测试耗时如下
table-base-duration.png
table 隐藏到显示 gif 图
table-base-6-8-s.gif
switch 从关到开 gif 图
table-base-switch-3-8-s.gif
为了验证我们自己写的耗时测试数据的准确性,这里在 switch 开关时,打开了 performance 录制,具体如下图
页面显示渲染耗时:4.524s,performance 中两个 Long Task:2.29s + 2.17,加上非 Long Task 部分,数据基本一致,因此我们自己写的耗时计算逻辑是基本准确的
table-base-switch-performance.gif
另外,开启 performance 录制时,比不录制时要稍微慢点。下面来开始优化吧!
列表中的开关切换时,table 虽然只是一个节点发生了变化,但依旧触发了完整的 vue patch 比对更新逻辑,耗时较久。
来看一个官方的解释:渲染机制 | Vue.js
(https://cn.vuejs.org/guide/extras/rendering-mechanism.html#compiler-informed-virtual-dom")
vue-render-logic.png
理论上,减少响应式数据依赖,就可以提升性能。
shallowRef() 是 ref() 的浅层作用形式。仅当 xx.value 发生变更时,才触发响应更新,减少深层次的响应依赖,可以提升 patch 比对性能。
参考:指南 - 减少大型不可变结构的响应性开销
(https://cn.vuejs.org/guide/best-practices/performance.html#reduce-reactivity-overhead-for-large-immutable-structures )
这里主要修改两种数据从 ref 到 shallowRef
这里有个问题,把 data、columns 改为 shallowRef 对功能会不会有影响?
基于以上三点,在我们业务中,这个修改是可行的。提醒:如果想在你自己的项目中使用该优化,需要先做好测试。
下面来看具体修改细节
当前最新的版本是 2.2.8,打开 element-plus/releases ( https://github.com/element-plus/element-plus/releases ),下载最新版本代码,将 table 目录(element-plus-2.2.28/packages/components/table) copy 到项目中的 src/table 下,删除目中无用的 __test__ 测试目录
新开一个路由,/new 指定到一个新增的 table 组件内,相比原先 table 组件,只增加一行代码,当前组件内使用我们自定义修改的 table。
完整代码参见:2-table-use-source | table-performance-demo
(https://github.com/zuoxiaobai/table-performance-demo/tree/2-table-use-source)
引入后报错 [plugin:vite:import-analysis] Failed to resolve import "@element-plus/directives" from "src\table\src\table.vue". Does the file exist?
element-table-error.png
做一些修改,让代码可以在我们自己的项目中跑起来,方便修改、调试源码
在 table 目录中搜索 \@element-plus 相关关键字,并进行批量替换
搜索 `@element-plus/components` 改为直接从 element-plus 引入
在 src/table/src/store/watcher.ts 中,将 data 和 columns 数据从 ref 改为 shallowRef,具体代码参:table-ref-shallowRef | table-performance-demo
("https://github.com/zuoxiaobai/table-performance-demo/tree/table-ref-shallowRef")
另外在 中 表格前面增加下面一行,标记调用的是我们修改的 table 组件
table 渲染、switch 切换测试耗时如下
table-ref-shallow-ref-duration.png
table 隐藏到显示 gif 图
table-ref-shallowRef.gif
switch 从关到开 gif 图
table-ref-shallowRef-switch.gif
当页面卡顿时,可以通过 performance 测试性能。下图是点击 switch 开关后的性能数据。可以看到
switch-performance-test.png
我们来查看这个函数耗时的原因,主要是在 tr 渲染时调用该函数,计算每列的宽度
具体实现如下,只用到了 realWidth, width 属性,且 columns.value 是响应式依赖,可以修改为非响应式数据,看是否能减少耗时。
这里我们新建 optimizeColumns 变量,存储函数中使用的 realWidth 和 width,将这个非响应式数据传入到 getColspanRealWidth 函数内部使用,
完整代码参见 getColspanRealWidth-optimize | table-performance-demo
( "https://github.com/zuoxiaobai/table-performance-demo/tree/getColspanRealWidth-optimize")
// src\table\src\table-body\render-helper.ts
const optimizeColumns = columns.value.map((item) => {
return { realWidth: item.realWidth, width: item.width };
});
columns.value.map((column, cellIndex) => {
// ...
columnData.realWidth = getColspanRealWidth(
optimizeColumns, // 传入函数内部时,使用非响应式数据
colspan,
cellIndex
);
// ...
})
修改好后再次测试性能,惊喜的发现,这个函数的耗时从 200ms+ 下降到 1ms 内,render 性能明显提升。1.54s + 1.45s = 2.99s
getColspanRealWidth-optimize.png
table 渲染、switch 切换测试耗时如下
get-width-optimize-perf.png
table 隐藏到显示 gif 图
get-width-optimize-table.gif
switch 从关到开 gif 图
get-width-optimize-switch.gif
经过上面的优化后,我们意识到,即使是很细微的响应式数据优化,也会对性能带来较大影响。那业务逻辑中是否也存在这样的数据呢?
于是采用注释 + 将 el-table-column 插槽换成静态节点 <span>123</span> 的方法,测试具体是哪里耗时较长,然后针对性优化。
经过测试,发现将自定义列中的 el-tooltip 换成静态节点后,性能有极大提升。
placement="top-start"
:disabled="!(item.prop === column1 && scope.row[item.prop])"
>
{{ "tooltip显示" + scope.row[item.prop] }}
{{ scope.row[item.prop] }}
</el-tooltip> --> <span>123</span></template></el-table-column>如下图,switch 开关切换耗时从 2.7s 左右减少到 0.5s 左右。performance 面板可以看到 patch 基本没有了,应该是模板编译时静态节点标记后,更新时就不用比对了。
tooltip-static-node-test.png
基于这个思路,el-tooltip 组件会成倍的增加 patch 比对耗时,减少这个节点数量即可增强性能。
为了少些一些代码,el-tooltip 使用 disabled 属性,用于在特定场景下隐藏 tooltip,这一部分数据可以不使用 el-tooltip 节点,改动如下,使用 v-if 替换 disabled 属性功能,这样虽然会有重复代码,但可以减少节点数。
placement="top-start"
:disabled="!(item.prop === column1 && scope.row[item.prop])"
>
{{ "tooltip显示" + scope.row[item.prop] }}
{{ scope.row[item.prop] }}
--> <spanv-if="!(item.prop === column1 && scope.row[item.prop])"> {{ scope.row[item.prop] }}</span><el-tooltipv-elseplacement="top-start"><template #content><span>{{ "tooltip显示" + scope.row[item.prop] }}</span></template><span>{{ scope.row[item.prop] }}</span></el-tooltip></template>再次测试性能,可以看到性能并没有下降多少,switch 开关切换可以做到 0.5s 左右刷新
tooltip-optimize.png
table 渲染、switch 切换测试耗时如下
tooltip-optimize-pref.png
table 隐藏到显示 gif 图
tooltip-optimize-table.gif
switch 从关到开 gif 图
tooltip-optimize-switch.gif
如下图,通过 3 个小的细节改动,将 table 渲染耗时从 6.88s 减少到 1s 左右,平均减少 85% 渲染耗时,用户体验基本达到预期。
完整 demo github 地址:
https://github.com/zuoxiaobai/table-performance-demo
pref-summary.png
在 vue3 项目中,响应式数据这块要特别注意。当遇到比较慢的场景时,建议采用如下方法进行性能优化