最近公司需要使用vue重构以前的项目,为了节省时间快速开发选择了使用element
不得不说,咋一看element的功能很全面样式,该有的都用,但是我们的项目对性能要求比较高,特别是表格
开发过程比较顺利各功能实现都很不难,但是性能测试确成了问题,分页的情况下单页100条就不怎么流畅了,更别说要求不分页5000条,直接加载过程中内存爆掉卡死了,于是开始了分析源码的路;
找到element/packages/table/src表格的代码都在这里
入口是table.vue 我们要看的是具体数据生成部分 看名字就知道是table-body.js
点进去一看,你基本就知道性能慢是个什么情况了,整体使用vue的render方法生成整个界面,整个table部分
render(h) { | |
const columnsHidden = this.columns.map((column, index) => this.isColumnHidden(index)); | |
return ( | |
<table | |
class="el-table__body" | |
cellspacing="0" | |
cellpadding="0" | |
border="0"> | |
<colgroup> | |
{ | |
this._l(this.columns, column => <col name={ column.id } />) | |
} | |
</colgroup> | |
<tbody> | |
{ | |
this._l(this.data, (row, $index) => | |
[<tr | |
style={ this.rowStyle ? this.getRowStyle(row, $index) : null } | |
key={ this.table.rowKey ? this.getKeyOfRow(row, $index) : $index } | |
on-dblclick={ ($event) => this.handleDoubleClick($event, row) } | |
on-click={ ($event) => this.handleClick($event, row) } | |
on-contextmenu={ ($event) => this.handleContextMenu($event, row) } | |
on-mouseenter={ _ => this.handleMouseEnter($index) } | |
on-mouseleave={ _ => this.handleMouseLeave() } | |
class={ [this.getRowClass(row, $index)] }> | |
{ | |
this._l(this.columns, (column, cellIndex) => { | |
const { rowspan, colspan } = this.getSpan(row, column, $index, cellIndex); | |
if (!rowspan || !colspan) { | |
return ''; | |
} else { | |
if (rowspan === 1 && colspan === 1) { | |
return ( | |
<td | |
style={ this.getCellStyle($index, cellIndex, row, column) } | |
class={ this.getCellClass($index, cellIndex, row, column) } | |
on-mouseenter={ ($event) => this.handleCellMouseEnter($event, row) } | |
on-mouseleave={ this.handleCellMouseLeave }> | |
{ | |
column.renderCell.call( | |
this._renderProxy, | |
h, | |
{ | |
row, | |
column, | |
$index, | |
store: this.store, | |
_self: this.context || this.table.$vnode.context | |
}, | |
columnsHidden[cellIndex] | |
) | |
} | |
</td> | |
); | |
} else { | |
return ( | |
<td | |
style={ this.getCellStyle($index, cellIndex, row, column) } | |
class={ this.getCellClass($index, cellIndex, row, column) } | |
rowspan={ rowspan } | |
colspan={ colspan } | |
on-mouseenter={ ($event) => this.handleCellMouseEnter($event, row) } | |
on-mouseleave={ this.handleCellMouseLeave }> | |
{ | |
column.renderCell.call( | |
this._renderProxy, | |
h, | |
{ | |
row, | |
column, | |
$index, | |
store: this.store, | |
_self: this.context || this.table.$vnode.context | |
}, | |
columnsHidden[cellIndex] | |
) | |
} | |
</td> | |
); | |
} | |
} | |
}) | |
} | |
</tr>, | |
this.store.isRowExpanded(row) | |
? (<tr> | |
<td colspan={ this.columns.length } class="el-table__expanded-cell"> | |
{ this.table.renderExpanded ? this.table.renderExpanded(h, { row, $index, store: this.store }) : ''} | |
</td> | |
</tr>) | |
: '' | |
] | |
).concat( | |
<el-tooltip effect={ this.table.tooltipEffect } placement="top" ref="tooltip" content={ this.tooltipContent }></el-tooltip> | |
) | |
} | |
</tbody> | |
</table> | |
); | |
}, |
我不太清楚render生成界面的时候是否会在数据变更后整体重新渲染界面,就算不会也会有一个变更判断过程,而他是实实在在的重新生成了整个table的vnode对象,还包含一堆子集,然后返回渲染,这个过程感觉不会快,特别是数据量大的时候
中间的思考过程略过,对比了vue的另一款table控件的源代码vue-easytable并试验了他的性能,决定第一步就是将界面生成方式改为template,并且将点击事件进行了委托
别的就不说了上代码
<template>
<table
class="el-table__body"
cellspacing="0"
cellpadding="0"
border="0">
<colgroup>
<col v-for="col in columns"
:key="col.id"
:name="col.id"
:width="col.width"/>
</colgroup>
<tbody
@click="handleClick"
@dblclick="handleDoubleClick"
@contextmenu="handleContextMenu">
<tr v-for="(row,$index) in data"
:style="trStyle(row, $index)"
:key="trKey(row, $index)"
@mouseenter="handleMouseEnter($index)"
@mouseleave="handleMouseLeave"
:class="trClass(row, $index)"
:index="$index">
<td v-for="(column,cellIndex) in columns"
v-if="getSpan(row, column, $index, cellIndex)"
:style="getCellStyle($index, cellIndex, row, column)"
:class="getCellClass($index, cellIndex, row, column)"
:rowspan="column._rowspan"
:colspan="column._colspan"
@mouseenter="handleCellMouseEnter($event, row)"
@mouseleave="handleCellMouseLeave">
到这里问题就来了,他的生成调用了其他文件里的方法table-column.js,水平有限学vue也没多久,他这个将具体的内容填充生成的方式没看懂,也没太多时间去研究
这个时候有两个思路
1.继续沿用他render的生成方式,我是重新写了一个小组件,在这个组件里去使用render生成就行,如下所示
<ElTableCell
:store="store"
:config="config(row,column,$index)"></ElTableCell>
2.牺牲他的灵活性,根据你具体的需求在这里手动判断生成想要的界面,你只需要把配置数据传递过来就行 , 这样做对性能提升是巨大的,正如 vue-easytable的编辑模式一样,模式少的可怜,这样也最快
如果你有多个编辑模式什么的,最好使用v-if,不要用v-show,可以大大提升加载速度,如下所示
<div v-else class="cell">
<div v-else-if="realtype(column)=='input'">
<div v-if="isHide(row)">{{row[column.keys]}}</div>
<el-input
v-else
type="input"
size="mini"
v-show="row.edit"
v-model="row[column.keys]">
</el-input>
</div>
<div v-else>
{{row[column.keys]}}
</div>
</div>
</td>
</tr>
</tbody>
</table>
</template>
经测试 第一种ElTableCell的方式有一定的性能提升 几百条应该没什么问题,不至于100就卡,但是1000条就会偶尔内存爆掉,单击双击编辑模式什么的速度提升比较明显;
第二种自定义模板的方式提升就比较大了,5000条没有问题,但是各种操作相对于第一种会慢不少,不过需要传递过来配置数据
这两种方式在数据量少的时候流畅度提升巨大,特别是几百条的时候感觉比较明显
原始生产方式的最后一段代码,tr生成的是展开行的具体内容(我不需要就没加),下面的是tooltip提示,暂时来说还不需要
this.store.isRowExpanded(row) | |
? (<tr> | |
<td colspan={ this.columns.length } class="el-table__expanded-cell"> | |
{ this.table.renderExpanded ? this.table.renderExpanded(h, { row, $index, store: this.store }) : ''} | |
</td> | |
</tr>) | |
: '' | |
] | |
).concat( | |
<el-tooltip effect={ this.table.tooltipEffect } placement="top" ref="tooltip" content={ this.tooltipContent }></el-tooltip> | |
) |
两种方式均需要再下面添加一些属性和方法,这里就不具体说明了,自己看
。。。有好的方式,可以教教我,感谢