Explorar o código

Table: optimize performance (#21330)

* Table: optimize performance

* Table: fix sync height
好多大米 %!s(int64=3) %!d(string=hai) anos
pai
achega
13d48cf511

+ 6 - 5
packages/table/src/config.js

@@ -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) {

+ 6 - 0
packages/table/src/store/index.js

@@ -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;

+ 72 - 73
packages/table/src/table-body.js

@@ -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 });
       }
     }
   }

+ 31 - 1
packages/table/src/table-layout.js

@@ -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;

+ 100 - 0
packages/table/src/table-row.js

@@ -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>
+    );
+  }
+};

+ 2 - 2
packages/table/src/table.vue

@@ -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