真真假假,el-table深度树状表格性能优化,快得不是一点半点

最近在实现一个深度树状可编辑表格的组件,从一开始使用的el-table的树状功能进行实现,但是在树状表格里面,支持可编辑表格,而且还支持动态编辑,复杂得不是一点半点,先来看看功能得截图 

就是一个深度动态可编辑树状结构表格,一般的场景很少用到,但是我这个工具需要用到。 先来说说使用el-table的树状结构表格实现过程, 通过动态化传参的方式进行实现

<template>
  <div class="tableDiv" v-if="isInitStart">
    <el-table
      :data="datalist"
      :key="updateKey"
      class="edit-table"
      row-key="id"
      border
      default-expand-all
      :cell-class-name="cellClassName"
      @cell-mouse-enter="handleCellMouseEnter"
      @cell-mouse-leave="handleCellMouseLeave"
      header-row-class-name="pm-edit-table-header"
      lazy
      ref="tableRef"
      :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
    >
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column
        header-align="center"
        v-for="(item, index) in tableColumn"
        :key="index"
        :prop="item.prop"
        :label="item.label"
        :width="item.width"
      >
        <!-- sortable -->
        <template slot-scope="scope">
          <div
            style="
              display: inline-block;
              width: 100%;
              display: flex;
              flex-direction: row;
              flex-flow: row;
              flex: 1;
            "
          >
            <div style="flex: 1">
              <MockEditor
                v-show="item.type == 'select'"
                v-model="scope.row.mock"
                :dataType.sync="scope.row.keyType"
                @change="
                  (val) => {
                    handleMockChange(val, scope.row);
                  }
                "
                :options="options.mockOptions"
                @addArrayItem="
                  (val) => {
                    handleAddArrayItem(val, scope.row);
                  }
                "
              >
              </MockEditor>
              <el-autocomplete
                v-show="
                  item.type != 'select' &&
                  (item.prop == 'keyName' || item.prop == 'dataCode')
                "
                :ref="'el_autocomplate_' + item.prop + '_' + scope.row.id"
                v-model="scope.row[item.prop]"
                style="padding-left: 5px; width: 100%"
                size="small"
                :disabled="
                  (Boolean(scope.row.isArrayValue) &&
                    item.prop != 'description') ||
                  (scope.row.keyType == 'Object' && item.prop == 'valueData') ||
                  (scope.row.keyType == 'Array' && item.prop == 'valueData')
                "
                :placeholder="item.placeholder"
                :fetch-suggestions="querySearch"
                @input="handleInputColumn(scope, item)"
                @select="
                  (item) => {
                    handleSelect(item, scope.row);
                  }
                "
              />
              <el-input
                v-show="
                  item.type != 'select' &&
                  item.prop != 'keyName' &&
                  item.prop != 'dataCode'
                "
                :ref="'el_input_' + item.prop + '_' + scope.row.id"
                v-model="scope.row[item.prop]"
                style="padding-left: 5px; width: 100%"
                size="small"
                :disabled="
                  (Boolean(scope.row.isArrayValue) &&
                    item.prop != 'description') ||
                  (scope.row.keyType == 'Object' && item.prop == 'valueData') ||
                  (scope.row.keyType == 'Array' && item.prop == 'valueData')
                "
                :placeholder="item.placeholder"
                :fetch-suggestions="querySearch"
                @input="handleInputColumn(scope, item)"
                @select="
                  (item) => {
                    handleSelect(item, scope.row);
                  }
                "
              />
            </div>
            <div
              v-if="index == tableColumn.length - 1 && !scope.row.isCreate"
              class="pm-edit-delete"
              @click="onDeleteRow(scope.row)"
            >
              <span><i class="el-icon-delete"></i></span>
            </div>
          </div>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

在以上的代码中,功能是相当的正常,原本以为一切都很顺利的时候,在后期进行大量数据渲染的时候,问题就出来,比如:当表格的数据过多的时候,每增加一个数据,因为使用updateKey更新的方式进行渲染更新,因此就会页面卡顿的情况,当数据量够多的时候,甚至会导致浏览器崩溃,同时每一次更新渲染,页面就就会回滚至顶部

尝试了局部更新、延时更新等等多种方法依然无法解决问题,在尝试多种方法无法解决的情况下,有一天回家的路上突然想到既然真的树状结构数据无法解决问题,那我做一个假的树状结构数据,不仅能够实现功能并且优化了加载性能。

假的树状表格实现思路:将树状结构的数据打成列表结构,对列表结构进行一级数据渲染,这样子就可以不用树状结构进行数据渲染,在更新的时候重新转换回树状结构并返回即可,说干就干,首先实现树状结构转换成列表结构

function treeToListEnhanced(treeData, options = {}) {
    const {
        childrenKey = 'children',
        defaultFields = {
            expanded: false,
            disableInput: false,
            hidden: false
        },
        keepChildren = false
    } = options;

    const result = [];

    function dfs(node, isArrayParent, level = 0, parentId = null) {
        if (!node) return;

        // 计算是否有子节点
        const hasChildren = !!(node[childrenKey] && node[childrenKey].length > 0);

        // 处理自定义字段
        const expanded = node.expanded !== undefined ? node.expanded : defaultFields.expanded;
        let disableInput = node.disableInput !== undefined ? node.disableInput : defaultFields.disableInput;
        const hidden = node.hidden !== undefined ? node.hidden : defaultFields.hidden;
       



        // 创建新节点
        const newNode = {
            ...node,
            level,
            parentId,
            hasChildren,
            expanded,
            disableInput,
            hidden
        };

        // 如果不保留children字段则删除
        if (!keepChildren) {
            delete newNode[childrenKey];
        }

        result.push(newNode);

        // 递归处理子节点
        if (hasChildren) {
            for (const child of node[childrenKey]) {
                if (isArrayParent) {
                    child.disableInput = true
                }
                dfs(child, child.keyType == 'Array', level + 1, node.id);
            }
        }
    }

    // 处理每个根节点
    for (const rootNode of treeData) {
        dfs(rootNode, rootNode.keyType == 'Array');
    }

    return result;
}

再使用返回的数据并使用el-table进行列表渲染,再将列表表格的数据重新渲染回树状表格数据

export function treeToListIterative(tree) {
    return treeToListEnhanced(tree, {
        defaultFields: {
            expanded: false,
            disableInput: false,
            hidden: false
        }
    })
    const result = [];
    if (!tree || !Array.isArray(tree)) return result;

    // 使用队列实现广度优先遍历
    const queue = tree.map(node => ({
        node,
        level: 0,
        parentId: null, // 根节点的parentId设为null更合理
        expanded: node.expanded || false,
        disableInput: node.disableInput || false,
        hidden: node.hidden || false
    }));

    while (queue.length > 0) {
        const current = queue.shift();
        const { node, level, parentId, expanded, disableInput, hidden } = current;

        // 创建新节点并保留除children外的所有属性
        const newNode = {
            ...node,
            level,
            parentId,
            expanded,
            disableInput,
            hidden,
            hasChildren: !!(node.children && node.children.length)
        };

        delete newNode.children; // 移除children属性

        result.push(newNode);

        // 处理子节点
        if (node.children && node.children.length) {
            for (const child of node.children) {
                queue.push({
                    node: child,
                    level: level + 1,
                    parentId: node.id,
                    expanded: child.expanded || false,
                    disableInput: child.disableInput || false,
                    hidden: child.hidden || false
                });
            }
        }
    }

    return result;
}

以上就是我实现通过巧妙思路解决深度树状结构表格数据渲染优化的过程,不仅优化了树状结构数据加载的性能,而且还修复了历史存在的一些如卡顿、回滚等问题。如想探讨详细思路,可留言联系。

posted @ 2025-06-18 12:00  brushwei  阅读(3)  评论(0)    收藏  举报