0%

element-table + dialog出现卡顿

背景

​ 在编写前端功能时候,最近比较频繁的使用了Element中的Table以及Dialog结合,这样可以方便对某一行的数据进行展示或者操作,具体效果如下(点击某行的查看,弹出对于该行的一个内容展示或者操作):

element-table-dialog卡顿-1.png

​ 但是随着表格的行数增加,会出现明显的卡顿现象,具体样式如下(并且能明显发现Dialog的边特别粗):

element-table-dialog卡顿-2.png

我是华丽的分割线~ (更新于 2021-9-18

基于评论的建议,发现整个事件在处理上有点问题,属于头疼医头,脚疼医脚。没有去找到根本原因,而仅仅是去找了解决方案😂。在出现了这个问题后,就马上去找解决方案, 但为什么存在上述的原因,却没有仔细去思考(后续一定要注意),实际原因是:因为将Dialog放在了Table的scope范围内,所以导致有多少行就会存在多少个Dialog。

最简单的解决办法: 将Dialog代码从Table的scope范围内移出即可。

原来的错误使用场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<el-table :data="tableData" border style="width: 100%">
<el-table-column label="操作" width="100">
<template slot-scope="scope">
<el-button @click="dialogVisible = true" type="text" size="small">查看</el-button>
<!-- Dialog位置不应该放在此处,导致处于Table的行所在的层级 -->
<el-dialog title="提示" :visible.sync="dialogVisible" width="30%" :before-close="handleClose" :append-to-body='true'>
<span>这是一段信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>
</template>
</el-table-column>
</el-table>
</template>

<script>
export default {
data() {
return {
dialogVisible: false,
tableData: [{date: "2016-05-02"}, {date: "2016-05-02"}, {date: "2016-05-02"}, {date: "2016-05-02"}, {date: "2016-05-02"}],
};
},
};
</script>

正确使用方式 (唯一的区别在于上面代码dialog在scope内,下面代码不在)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<el-table :data="tableData" border style="width: 100%">
<el-table-column label="操作" width="100">
<template slot-scope="scope">
<el-button @click="dialogVisible = true" type="text" size="small">查看</el-button>
</template>
<el-dialog title="提示" :visible.sync="dialogVisible" width="30%" :before-close="handleClose" :append-to-body='true'>
<span>这是一段信息</span>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取 消</el-button>
<el-button type="primary" @click="dialogVisible = false">确 定</el-button>
</span>
</el-dialog>
</el-table-column>
</el-table>
</template>

<script>
export default {
data() {
return {
dialogVisible: false,
tableData: [{date: "2016-05-02"}, {date: "2016-05-02"}, {date: "2016-05-02"}, {date: "2016-05-02"}, {date: "2016-05-02"}],
};
},
};
</script>

至此,问题解决的又快速,代码写的又简单。但原来却花了那么老大的功夫去折腾下面那一堆代码╮(╯Д╰)╭ 。


我是华丽的分割线~ (更新于 2021-5-19)

排查过程

​ 查看Demo代码发现, Dialog显示框是通过属性 :visible.sync="centerDialogVisible" 来控制的(关于visible.sync可参考官网),但由于该变量仅有一个,绑定在了所有的行上面,猜想:点击任意一行,都会导致触发多次Dialog的打开。通过如下代码来进行验证下:(主动触发Dialog open事件参考链接

1
2
3
4
5
6
7
<!-- 通过添加触发Dialog中的Open event打印出共打开多少次Dialog,来验证猜想 -->
<el-dialog
...
:visible.sync="centerDialogVisible"
@open='open'
...>

1
2
3
4
5
6
methods: {
open() {console.log("test dialog open");}
},

// 最终控制台输出:40 test dialog open test-demo.vue:75
// tableData的长度为20

问题的根本原因:由于仅仅是绑定了单个变量 centerDialogVisible, 所以随着表格数据的增加,对应打开的Dailog个数等于表格数据的长度 X 2。

新增疑问:为什么触发Dialog自带的Open事件数量刚好是长度的2倍?

解答:通过Visual Studio Code工具调试后发现,代码在点击了查看按钮打开Dialog后,会触发 flushSchedulerQueue 方法,在这个过程中, 虽然从代码层面仅仅对 visible 进行了一次修改,但由于Element内部的 el-tableel-dialog自带的Watcher监听,最终将 visible 的变化在队列中存放了两次,所以内部的open方法也被触发了两次。(有兴趣的可深入了解 element-ui.common.js)

解决办法

方案一:Dialog 通过每一行的特有属性控制

既然是由于绑定了同一个变量centerDialogVisible,那么可以通过对tableData的每一行添加一个centerDialogVisible属性,具体实行的效果如下:

1
2
3
4
5
6
<el-dialog title="提示" :visible.sync="scope.row.dialogFlag" width="30%" @open='open' center>
...
<el-button @click="closeDialog(scope.row)">取 消</el-button>
<el-button type="primary" @click="closeDialog(scope.row)">确 定</el-button>
</span>
</el-dialog>

该方案能够解决卡顿以及黑边的问题,但存在如下问题:

  • 问题一: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
    18
    handleWrapperClick: 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
    4
    close() {
    var closeRow = this.tableData[this.showDialogRowIndex]
    this.$set(this.tableData, this.showDialogRowIndex, closeRow)
    },
  • 问题二:需要变量使用响应式解决tableData数据更新后页面无响应的问题。

    1
    2
    3
    4
    handleDialog(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
2
3
4
5
6
7
<el-dialog 
...
:visible.sync="scope.row.dialogFlag"
v-if="dialogStatus.rowIndex === scope.row.count && dialogStatus.dialogStatus"
>
...
</el-dialog>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
data() {
return {
dialogStatus: {
rowIndex: -1,
dialogStatus: false
},
}
}

methods: {
handleDialog(row) {
this.showDialogRowIndex = row.count // count 属性可以用 row自带的index属性替换,等同于行号, 从0开始
this.centerDialogVisible = true
this.dialogStatus.rowIndex = row.count
this.dialogStatus.dialogStatus = true
},
closeDialog(row) {
this.centerDialogVisible = false
this.showDialogRowIndex = -1
this.dialogStatus.rowIndex = -1
this.dialogStatus.dialogStatus = false
},
close() {
this.count = 0
this.dialogStatus.rowIndex = -1
this.dialogStatus.dialogStatus = false
},
}

该方案比较完美并且简单的解决了卡顿和Dialog黑边的场景,但会存在黑屏效果。可以使用 Dialog的属性 :append-to-body='true' 解决该问题。

element-table-dialog卡顿-3.png
1
2
3
<el-dialog :append-to-body='true'>
...
</el-dialog>

PS:暂时仅思考了上述两种解决方案,如果有其他前端大佬有更好的解决方案可在评论区留言。

总结

​ 虽然该问题不是一个太复杂的问题,但是通过这样一次整个问题的排查以及多种方案的思考,还是获益匪浅。其中包括了解了怎么去debug node_modules 中第三方源码的方法,如果一直秉持着这种态度去工作,时间久了肯定是会有质的变化。

------------- 本 文 结 束 感 谢 您 的 阅 读 -------------