Element-UI填坑——el-tabs与el-table嵌套使用的坑

前言

昨天写了一篇 JavaScript 小数精度遇到的坑 (传送门),今天又写一篇填坑心得,别问我为什么,坑就是这么多。不过话说回来,Element-UI 的 BUG 是真的有点多啊,特别是 1.x 版本,然而由于历史原因,我们项目还在用 1.x 版本😐😐😐。

发现问题

现在开始进入正题,说下如何入坑的,有2个标签页,每个标签页下面有一个表格展示数据,可以切换顶部的标签来切换显示哪一个表格。用 el-tabsel-table 两个组件嵌套就可以搞定,没什么问题😊。可是测试的时候发现一个问题,当重新加载数据时,隐藏的那个标签页下面的表格 body 部分高度不知为啥消失了。用调试工具查看元素时,发现表格 .el-table__body-wrapper 这个元素有个内联样式 height: 1px,这一看就是 Element-UI 框架计算出来的高度,于是就向这个方向去排查。

如何解决?

既然有了方向,那么该如何解决呢?当然是去看源码了,我是自己在本地的 node_modules 目录下面看的,就在 /node_modules/element-ui/packages/table 这个文件夹下面,然后看 index.js 里面引用的是 'src/table',那就从 src/table.vue (源码传送门) 这个组件开始看吧。

由于之前已经发现了 .el-table__body-wrapper 这个元素的内联样式计算有问题,所以直接找到 <template>这个元素,发现果然有个动态绑定的样式 bodyHeight,这是一个 Vue 的计算属性 bodyHeight (源码传送门)。简单的分析之后,发现高度的计算都与 this.layout 这个对象相关,于是,我将目标转到 this.layout 上面。

继续分析 this.layout,这是由 TableLayout 类 (源码传送门) 实例化出来的一个对象。再结合之前计算高度的地方引用了 this.layout.bodyHeight,所以直接搜索 bodyHeight,发现都在这个类的 updateHeight 方法里。大致看了下这个方法的代码,就是通过获取一些元素的高度,进行一些计算的操作(说了跟没说一样😏),反正看不出来有什么问题。

所以,该怎么办?

好像碰到瓶颈了,该怎么办?我的第一个想法就是断点调试呗,这是在 node_modules 目录下的,怎么断点?我的解决办法就是继承 el-table 组件,然后在 Vue 生命周期的 created 方法里面覆盖 this.layout 的值,代码如下:

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
// my-table.js
import Vue from 'vue'
import ElTable from 'element-ui/lib/table'

// 这里把 table-layout.js 复制过来,在 updateHeight 里打个断点
// 不能继承这个类,会报错:https://github.com/babel/babel/issues/4269
class TableLayout {
updateHeight() {
debugger
...
}
}

export default {
extends: ElTable,
created() {
// this.layout 创建方法跟它之前一致
// 因为 store 之前已经绑定在 data 上了,所以现在只需引用 this.store 就行了
this.layout = new TableLayout({
store: this.store,
table: this,
fit: this.fit,
showHeader: this.showHeader
})
}
}

然后在将 my-table.js 引入作为 Vue 组件,将 el-table 替换成 my-table。到这里,已经成功在刚才的方法里打上了一个断点。

切换到之前出 BUG 的场景,成功进入断点,单行调试。发现组件的高度为 0px,再仔细一看,竟然是 .el-tab-pan 这个元素 display: none,它的子元素肯定获取不到高度啊。而这个元素隐藏应该是 el-tabs 组件切换标签页造成的。很明显锅不在 el-table,而在 el-tabs 啊👀。

那就看 el-tabs 组件的源码吧 (源码传送门),看到 .el-tab-pane 这个元素的显隐是通过 v-show="active" 控制的,而 active 是一个 Vue 的计算属性,就是判断当前的面板是否处于激活状态。到这里,我们终于知道了这BUG产生的原因了。

所以,然后呢?

Element-UI 的 el-tabs 组件是通过 v-show 来控制标签页的显隐,这非常简单暴力,但同时也导致了我们刚开始说的计算高度失效这个问题。所以,我们可以就让所有的标签页下的内容都“显示”出来,然后通过 z-index 来控制显隐,注意背景不要透明(当然也有其他实现方式,只要能获取到表格的真实高度就行)。直接改写 el-tab-pane 组件,代码如下:

template 部分:

1
2
3
4
5
<template>
<div class="el-tab-pane" :style="paneStyle">
<slot></slot>
</div>
</template>

这里把 v-show 换成绑定一个动态样式,用于动态设置 z-index

script 部分:

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
export default {
name: 'ElTabPane',

componentName: 'ElTabPane',

props: {
label: String,
labelContent: Function,
name: String,
closable: Boolean,
disabled: Boolean,
},

data() {
return {
index: null,
};
},

computed: {
isClosable() {
return this.closable || this.$parent.closable;
},
active() {
return this.$parent.currentName === (this.name || this.index);
},
// 设置面板的 z-index
paneStyle() {
return {
zIndex: this.active ? 1 : 0,
};
},
},

mounted() {
this.$parent.addPanes(this);
},

destroyed() {
if (this.$el && this.$el.parentNode) {
this.$el.parentNode.removeChild(this.$el);
}
this.$parent.removePanes(this);
},

watch: {
label() {
this.$parent.$forceUpdate();
},
},
}

这里在计算属性这里添加了一个 paneStyle,用于动态设置面板的样式,激活状态 z-index 设置为1,其他情况设置为0,相当于激活状态的面板总是在顶层。

style 部分 (scss)

1
2
3
4
5
6
7
8
9
10
.el-tabs .el-tabs__content {
display: block;
.el-tab-pane {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
}

这里由于 .el-tabs__content 之前被重写为 flex 布局,所以这里重新改成 block,然后把 .el-tab-pane 改成绝对定位,收工👌。别忘了把 el-tab-pane 换成重写之后的。

来点感想

首先,element-ui的坑有点多,相信每个框架都有各种坑,我们踩坑的过程其实就是给自己涨经验。所以,不要怕踩坑。还有一点我想说的就是,不要怕看源码,源码没有想象的那么可怕。多看看源码可以对自己的提升肯定是有好处的(好像又是一句废话😏)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
   ┏┓  ┏┓+ +
  ┏┛┻━━━┛┻┓ + +
  ┃      ┃  
  ┃   ━   ┃ ++ + + +
  ████━████ ┃+
  ┃       ┃ +
  ┃   ┻   ┃
  ┃      ┃ + +
  ┗━┓   ┏━┛
    ┃   ┃           
    ┃   ┃ + + + +
    ┃   ┃    Code is far away from bug with the animal protecting       
    ┃   ┃ +     神兽保佑,代码无bug  
    ┃   ┃
    ┃   ┃  +         
    ┃    ┗━━━┓ + +
    ┃      ┣┓
    ┃       ┏┛
    ┗┓┓┏━┳┓┏┛ + + + +
    ┃┫┫ ┃┫┫
    ┗┻┛ ┗┻┛+ + + +