背景
在编写前端功能时候,最近比较频繁的使用了Element中的Table以及Dialog结合,这样可以方便对某一行的数据进行展示或者操作,具体效果如下(点击某行的查看,弹出对于该行的一个内容展示或者操作):
但是随着表格的行数增加,会出现明显的卡顿现象,具体样式如下(并且能明显发现Dialog的边特别粗):
我是华丽的分割线~ (更新于 2021-9-18)
基于评论的建议,发现整个事件在处理上有点问题,属于头疼医头,脚疼医脚。没有去找到根本原因,而仅仅是去找了解决方案😂。在出现了这个问题后,就马上去找解决方案, 但为什么存在上述的原因,却没有仔细去思考(后续一定要注意),实际原因是:因为将Dialog放在了Table的scope范围内,所以导致有多少行就会存在多少个Dialog。
最简单的解决办法: 将Dialog代码从Table的scope范围内移出即可。
❌ 原来的错误使用场景
1 | <template> |
✅ 正确使用方式 (唯一的区别在于上面代码dialog在scope内,下面代码不在)
1 | <template> |
至此,问题解决的又快速,代码写的又简单。但原来却花了那么老大的功夫去折腾下面那一堆代码╮(╯Д╰)╭ 。
我是华丽的分割线~ (更新于 2021-5-19)
排查过程
查看Demo代码发现, Dialog显示框是通过属性 :visible.sync="centerDialogVisible"
来控制的(关于visible.sync可参考官网),但由于该变量仅有一个,绑定在了所有的行上面,猜想:点击任意一行,都会导致触发多次Dialog的打开。通过如下代码来进行验证下:(主动触发Dialog open事件参考链接)
1 | <!-- 通过添加触发Dialog中的Open event打印出共打开多少次Dialog,来验证猜想 --> |
1 | methods: { |
问题的根本原因:由于仅仅是绑定了单个变量 centerDialogVisible, 所以随着表格数据的增加,对应打开的Dailog个数等于表格数据的长度 X 2。
新增疑问:为什么触发Dialog自带的Open事件数量刚好是长度的2倍?
解答:通过Visual Studio Code
工具调试后发现,代码在点击了查看按钮打开Dialog后,会触发 flushSchedulerQueue
方法,在这个过程中, 虽然从代码层面仅仅对 visible 进行了一次修改,但由于Element内部的 el-table
和 el-dialog
自带的Watcher监听,最终将 visible 的变化在队列中存放了两次,所以内部的open方法也被触发了两次。(有兴趣的可深入了解 element-ui.common.js)
解决办法
方案一:Dialog 通过每一行的特有属性控制
既然是由于绑定了同一个变量centerDialogVisible
,那么可以通过对tableData的每一行添加一个centerDialogVisible属性,具体实行的效果如下:
1 | <el-dialog title="提示" :visible.sync="scope.row.dialogFlag" width="30%" @open='open' center> |
该方案能够解决卡顿以及黑边的问题,但存在如下问题:
问题一:close-on-click-modal(是否可以通过点击 modal 关闭 Dialog)和close-on-press-escape(是否可以通过按下 ESC 关闭 Dialog)无法触发Dialog关闭;
对Demo中的代码深入研究下源码会发现分别触发如下的代码,最终都是通过 hide()方法将Dialog关闭。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18handleWrapperClick: function handleWrapperClick() {
if (!this.closeOnClickModal) return;
this.handleClose();
},
handleClose: function handleClose() {
if (typeof this.beforeClose === 'function') {
this.beforeClose(this.hide);
} else {
this.hide();
}
},
hide: function hide(cancel) {
if (cancel !== false) {
this.$emit('update:visible', false);
this.$emit('close');
this.closed = true;
}
},通过Dialog自带的close事件查看对应绑定的Table值,可以发现Table中对应的
dialogFlag
已经更新了,但至于无法触发Dialog关闭的原因同问题2,因为绑定的变量不是响应式。解决方案如下(新增自定义的close方法):1
2
3<el-dialog title="提示" :visible.sync="scope.row.dialogFlag" width="30%" @close='close' center>
...
</el-dialog>1
2
3
4close() {
var closeRow = this.tableData[this.showDialogRowIndex]
this.$set(this.tableData, this.showDialogRowIndex, closeRow)
},问题二:需要变量使用响应式解决tableData数据更新后页面无响应的问题。
1
2
3
4handleDialog(row) {
row.dialogFlag = true
this.$set(this.tableData, row.count, row); // 不添加响应式修改table数据的话,则Dialog无法正常打开
},
方案二:通过 v-if 与 :visible.sync 共同控制
直接通过定义一个变量 dialogStatus,响应式的去控制Dialog的展示,具体核心代码如下:
这样就不需要通过 this.$set(this.tableData, this.showDialogRowIndex, closeRow)
强制刷新Table信息。
PS:为什么使用 v-if 可以参考:v-if 与 v-show的区别
1 | <el-dialog |
1 | data() { |
该方案比较完美并且简单的解决了卡顿和Dialog黑边的场景,但会存在黑屏效果。可以使用 Dialog的属性 :append-to-body='true'
解决该问题。
1 | <el-dialog :append-to-body='true'> |
PS:暂时仅思考了上述两种解决方案,如果有其他前端大佬有更好的解决方案可在评论区留言。
总结
虽然该问题不是一个太复杂的问题,但是通过这样一次整个问题的排查以及多种方案的思考,还是获益匪浅。其中包括了解了怎么去debug node_modules
中第三方源码的方法,如果一直秉持着这种态度去工作,时间久了肯定是会有质的变化。