Table: optimize performance (#21330)

* Table: optimize performance

* Table: fix sync height
This commit is contained in:
好多大米 2021-11-18 11:24:07 +08:00 committed by GitHub
parent 6baf6dc706
commit 13d48cf511
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 217 additions and 81 deletions

View File

@ -35,12 +35,13 @@ export const cellForced = {
on-input={ this.toggleAllSelection }
value={ this.isAllSelected } />;
},
renderCell: function(h, { row, column, store, $index }) {
renderCell: function(h, { row, column, isSelected, store, $index }) {
return <el-checkbox
nativeOn-click={ (event) => event.stopPropagation() }
value={ store.isSelected(row) }
value={ isSelected }
disabled={ column.selectable ? !column.selectable.call(null, row, $index) : false }
on-input={ () => { store.commit('rowSelectedChanged', row); } } />;
on-input={ () => { store.commit('rowSelectedChanged', row); } }
/>;
},
sortable: false,
resizable: false
@ -67,9 +68,9 @@ export const cellForced = {
renderHeader: function(h, { column }) {
return column.label || '';
},
renderCell: function(h, { row, store }) {
renderCell: function(h, { row, store, isExpanded }) {
const classes = ['el-table__expand-icon'];
if (store.states.expandRows.indexOf(row) > -1) {
if (isExpanded) {
classes.push('el-table__expand-icon--expanded');
}
const callback = function(e) {

View File

@ -98,6 +98,7 @@ Watcher.prototype.mutations = {
}
this.updateTableScrollY();
this.syncFixedTableRowHeight();
},
filterChange(states, options) {
@ -111,6 +112,7 @@ Watcher.prototype.mutations = {
}
this.updateTableScrollY();
this.syncFixedTableRowHeight();
},
toggleAllSelection() {
@ -144,4 +146,8 @@ Watcher.prototype.updateTableScrollY = function() {
Vue.nextTick(this.table.updateScrollY);
};
Watcher.prototype.syncFixedTableRowHeight = function() {
Vue.nextTick(() => this.table.layout.syncFixedTableRowHeight());
};
export default Watcher;

View File

@ -6,6 +6,7 @@ import ElTooltip from 'element-ui/packages/tooltip';
import debounce from 'throttle-debounce/debounce';
import LayoutObserver from './layout-observer';
import { mapStates } from './store/helper';
import TableRow from './table-row.js';
export default {
name: 'ElTableBody',
@ -14,7 +15,8 @@ export default {
components: {
ElCheckbox,
ElTooltip
ElTooltip,
TableRow
},
props: {
@ -39,16 +41,25 @@ export default {
border="0">
<colgroup>
{
this.columns.map(column => <col name={ column.id } key={column.id} />)
this.columns
.filter((column, index) => !(this.columnsHidden[index] && this.fixed))
.map(column => <col name={column.id} key={column.id} />)
}
</colgroup>
<tbody>
{
data.reduce((acc, row) => {
return acc.concat(this.wrappedRowRender(row, acc.length));
const isSelected = this.store.isSelected(row);
const isExpanded = this.store.states.expandRows.indexOf(row) > -1;
return acc.concat(this.wrappedRowRender({
row,
$index: acc.length,
isSelected,
isExpanded
}));
}, [])
}
<el-tooltip effect={ this.table.tooltipEffect } placement="top" ref="tooltip" content={ this.tooltipContent }></el-tooltip>
<el-tooltip effect={this.table.tooltipEffect} placement="top" ref="tooltip" content={this.tooltipContent}></el-tooltip>
</tbody>
</table>
);
@ -71,6 +82,10 @@ export default {
hasExpandColumn: states => states.columns.some(({ type }) => type === 'expand')
}),
columnsHidden() {
return this.columns.map((column, index) => this.isColumnHidden(index));
},
firstDefaultColumnIndex() {
return arrayFindIndex(this.columns, ({ type }) => type === 'default');
}
@ -238,7 +253,7 @@ export default {
if (cell) {
const column = getColumnByCell(table, cell);
const hoverState = table.hoverState = {cell, column, row};
const hoverState = table.hoverState = { cell, column, row };
table.$emit('cell-mouse-enter', hoverState.row, hoverState.column, hoverState.cell, event);
}
@ -314,9 +329,18 @@ export default {
table.$emit(`row-${name}`, row, column, event);
},
rowRender(row, $index, treeRowData) {
getRowHeight(rowKey) {
const { fixed } = this;
if (!fixed) {
return null;
}
const height = (this.tableLayout.fixedColumnsBodyRowsHeight || {})[rowKey];
return typeof height === 'number' ? `${height}px` : height;
},
rowRender({ row, $index, treeRowData, isSelected, isExpanded }) {
const { treeIndent, columns, firstDefaultColumnIndex } = this;
const columnsHidden = columns.map((column, index) => this.isColumnHidden(index));
const rowClasses = this.getRowClass(row, $index);
let display = true;
if (treeRowData) {
@ -328,76 +352,51 @@ export default {
let displayStyle = display ? null : {
display: 'none'
};
return (<tr
style={ [displayStyle, this.getRowStyle(row, $index)] }
class={ rowClasses }
key={ this.getKeyOfRow(row, $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 }>
{
columns.map((column, cellIndex) => {
const { rowspan, colspan } = this.getSpan(row, column, $index, cellIndex);
if (!rowspan || !colspan) {
return null;
}
const columnData = { ...column };
columnData.realWidth = this.getColspanRealWidth(columns, colspan, cellIndex);
const data = {
store: this.store,
_self: this.context || this.table.$vnode.context,
column: columnData,
row,
$index
};
if (cellIndex === firstDefaultColumnIndex && treeRowData) {
data.treeNode = {
indent: treeRowData.level * treeIndent,
level: treeRowData.level
};
if (typeof treeRowData.expanded === 'boolean') {
data.treeNode.expanded = treeRowData.expanded;
// 表明是懒加载
if ('loading' in treeRowData) {
data.treeNode.loading = treeRowData.loading;
}
if ('noLazyChildren' in treeRowData) {
data.treeNode.noLazyChildren = treeRowData.noLazyChildren;
}
}
}
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,
this.$createElement,
data,
columnsHidden[cellIndex]
)
}
</td>
);
})
}
</tr>);
const height = this.getRowHeight($index);
const heightStyle = height ? {
height
} : null;
return (
<TableRow
style={[displayStyle, this.getRowStyle(row, $index), heightStyle]}
class={rowClasses}
key={this.getKeyOfRow(row, $index)}
nativeOn-dblclick={($event) => this.handleDoubleClick($event, row)}
nativeOn-click={($event) => this.handleClick($event, row)}
nativeOn-contextmenu={($event) => this.handleContextMenu($event, row)}
nativeOn-mouseenter={_ => this.handleMouseEnter($index)}
nativeOn-mouseleave={this.handleMouseLeave}
columns={columns}
row={row}
index={$index}
store={this.store}
context={this.context || this.table.$vnode.context}
firstDefaultColumnIndex={firstDefaultColumnIndex}
treeRowData={treeRowData}
treeIndent={treeIndent}
columnsHidden={this.columnsHidden}
getSpan={this.getSpan}
getColspanRealWidth={this.getColspanRealWidth}
getCellStyle={this.getCellStyle}
getCellClass={this.getCellClass}
handleCellMouseEnter={this.handleCellMouseEnter}
handleCellMouseLeave={this.handleCellMouseLeave}
isSelected={isSelected}
isExpanded={isExpanded}
fixed={this.fixed}
>
</TableRow>
);
},
wrappedRowRender(row, $index) {
wrappedRowRender({ row, $index, isSelected, isExpanded }) {
const store = this.store;
const { isRowExpanded, assertRowKey } = store;
const { treeData, lazyTreeNodeMap, childrenColumnName, rowKey } = store.states;
if (this.hasExpandColumn && isRowExpanded(row)) {
const renderExpanded = this.table.renderExpanded;
const tr = this.rowRender(row, $index);
const tr = this.rowRender({ row, $index, isSelected, isExpanded });
if (!renderExpanded) {
console.error('[Element Error]renderExpanded is required.');
return tr;
@ -430,7 +429,7 @@ export default {
treeRowData.loading = cur.loading;
}
}
const tmp = [this.rowRender(row, $index, treeRowData)];
const tmp = [this.rowRender({ row, $index, treeRowData, isSelected, isExpanded })];
// 渲染嵌套数据
if (cur) {
// currentRow 记录的是 index所以还需主动增加 TreeTable 的 index
@ -464,7 +463,7 @@ export default {
}
}
i++;
tmp.push(this.rowRender(node, $index + i, innerTreeRowData));
tmp.push(this.rowRender({ row: node, $index: $index + i, treeRowData: innerTreeRowData, isSelected, isExpanded }));
if (cur) {
const nodes = lazyTreeNodeMap[childKey] || node[childrenColumnName];
traverse(nodes, cur);
@ -478,7 +477,7 @@ export default {
}
return tmp;
} else {
return this.rowRender(row, $index);
return this.rowRender({ row, $index, isSelected, isExpanded });
}
}
}

View File

@ -25,6 +25,7 @@ class TableLayout {
this.bodyHeight = null; // Table Height - Table Header Height
this.fixedBodyHeight = null; // Table Height - Table Header Height - Scroll Bar Height
this.gutterWidth = scrollbarWidth();
this.fixedColumnsBodyRowsHeight = {};
for (let name in options) {
if (options.hasOwnProperty(name)) {
@ -113,11 +114,40 @@ class TableLayout {
const noData = !(this.store.states.data && this.store.states.data.length);
this.viewportHeight = this.scrollX ? tableHeight - (noData ? 0 : this.gutterWidth) : tableHeight;
setTimeout(() => {
this.syncFixedTableRowHeight();
});
this.updateScrollY();
this.notifyObservers('scrollable');
}
syncFixedTableRowHeight() {
const fixedColumns = this.store.states.fixedColumns;
const rightFixedColumns = this.store.states.rightFixedColumns;
if (fixedColumns.length + rightFixedColumns.length === 0) {
return;
}
const { bodyWrapper } = this.table.$refs;
const tableRect = bodyWrapper.getBoundingClientRect();
if (tableRect.height !== undefined && tableRect.height <= 0) {
return;
}
const bodyRows = bodyWrapper.querySelectorAll('.el-table__row') || [];
const fixedColumnsBodyRowsHeight = [].reduce.call(
bodyRows,
(acc, row, index) => {
const height =
row.getBoundingClientRect().height || 'auto';
acc[index] = height;
return acc;
},
{}
);
this.fixedColumnsBodyRowsHeight = fixedColumnsBodyRowsHeight;
};
headerDisplayNone(elm) {
if (!elm) return true;
let headerChild = elm;

View File

@ -0,0 +1,100 @@
export default {
name: 'ElTableRow',
props: [
'columns',
'row',
'index',
'isSelected',
'isExpanded',
'store',
'context',
'firstDefaultColumnIndex',
'treeRowData',
'treeIndent',
'columnsHidden',
'getSpan',
'getColspanRealWidth',
'getCellStyle',
'getCellClass',
'handleCellMouseLeave',
'handleCellMouseEnter',
'fixed'
],
render() {
const {
columns,
row,
index: $index,
store,
context,
firstDefaultColumnIndex,
treeRowData,
treeIndent,
columnsHidden = [],
isSelected,
isExpanded
} = this;
return (
<tr>
{
columns.map((column, cellIndex) => {
if (columnsHidden[cellIndex] && this.fixed) {
return null;
}
const { rowspan, colspan } = this.getSpan(row, column, $index, cellIndex);
if (!rowspan || !colspan) {
return null;
}
const columnData = { ...column };
columnData.realWidth = this.getColspanRealWidth(columns, colspan, cellIndex);
const data = {
store,
isSelected,
isExpanded,
_self: context,
column: columnData,
row,
$index
};
if (cellIndex === firstDefaultColumnIndex && treeRowData) {
data.treeNode = {
indent: treeRowData.level * treeIndent,
level: treeRowData.level
};
if (typeof treeRowData.expanded === 'boolean') {
data.treeNode.expanded = treeRowData.expanded;
// 表明是懒加载
if ('loading' in treeRowData) {
data.treeNode.loading = treeRowData.loading;
}
if ('noLazyChildren' in treeRowData) {
data.treeNode.noLazyChildren = treeRowData.noLazyChildren;
}
}
}
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,
this.$createElement,
data,
columnsHidden[cellIndex]
)
}
</td>
);
})
}
</tr>
);
}
};

View File

@ -115,7 +115,7 @@
:row-class-name="rowClassName"
:row-style="rowStyle"
:style="{
width: bodyWidth
width: layout.fixedWidth ? layout.fixedWidth + 'px' : ''
}">
</table-body>
<div
@ -176,7 +176,7 @@
:row-style="rowStyle"
:highlight="highlightCurrentRow"
:style="{
width: bodyWidth
width: layout.rightFixedWidth ? layout.rightFixedWidth + 'px' : '',
}">
</table-body>
<div