【Element-ui】el-table大数据量渲染卡顿问题

news2024/11/18 7:42:50

1、场景描述

在项目开发中,遇到在表格中一次性加载完的需求,且加载数量不少,有几百几千条,并且每条都可能有自己的下拉框,输入框来做编辑功能,此时普通的el-table肯定会导致浏览器卡死,那么怎么办呢?

2、解决方案

当然很多童鞋肯定会想到利用插件,其实我本人是不咋喜欢插件的,能自己写就自己写,毕竟插件可能也有bug或者啥的,万一出现了,作者不去改咋办,所以我总结了下面几个解决方法

提示:本篇博客基本都用的tsx + vue3 composition-api体验版 来写的噢,用vue2或者vue3模板语法写会更简单噢,照葫芦画瓢逻辑都是一样的,我这里就不写了,想了解相关的vue3知识我另一篇博客上有噢~

Vue3知识点学习

1、滚动触底分页(良)

当纵向滚动条触底的时候,加载新的数据到当前表格中,逻辑如下:

table.scrollTop + table.clientHeight === table.scrollHeight 

当上述成立时候触发加载,table为表格dom, 但是如果数据很多的话,每次滚动都会将新的tr加入到表格中,那表格tr dom总数还是会依次递增,dom一多照样卡死

2、滚动区间分页(良+)

如果你没有表格编辑功能,全是展示数据的话,那么这个解决方案已经完全可以了

比如你当前表格可显示区域能够展示十条数据,那么首次进来时,显示数据的区间为 [0, 9],每次表格滚动时,都动态的去获取当前展示的区间,其实这种方式是比较好的,为啥呢?因为不管你几千几万条数据,我同一时刻就只有10行,完全不会因为tr数量过多导致浏览器渲染卡顿,当然这可视区域能展示多少条数据,是有你表格可视高度和单行tr高度一起决定的

const selectWrap = table.querySelector('.el-table__body-wrapper');

const selectRow = table.querySelector('table tr');

展示tr数量 = Math.ceil(selectWrap.clientHeight / selectRow.clientHeight)

tsx:页面

setup() {
  return () => (
    <el-table
      data={visibleResult.value} // 可视区域的数据
    >
    </el-table>
  )
}

ts:逻辑

  /** 表格上展示的数据 */
  const visibleResult = computed(() => {
    return result.value.filter((_item, index) => {
      if (index < curStartIndex.value) {
        return false;
      } else if (index > curEndIndex.value) {
        return false;
      } else {
        return true;
      }
    });
  })

那么如何去控制这个滚动区间呢?这个方法很多,监听滚动条的scroll,当向下滚动或向上滚动,我们都可以监听到,然后改变curStartIndex和curEndIndex的值就可以改变啦,这样的话,光是只看数据倒是解决了,但是要是表格要实现编辑效果咋办?每行数据有十几个下拉框和输入框,你要知道,el-select dom层级很高,像这种el-select数量一多,就算你当前展示区域没有多少条数据,也会导致渲染卡顿的,所以就有下面的优化版方案

3、滚动区间分页 + 表格编辑(优)

当前表格可视区域有30条数据,每条数据有10个el-select和5个el-input(当然可能有童鞋会说,el-select dom层级高,那我就自己写select鸭~但是你自己写的未必有el-select好看且功能相当)。此时首次加载就很卡顿了,原因就是el-select多了,那么我首先想的是这些el-select又不是直接要用,为啥不在我点击这个单元格时候再弹出下拉呢?不点击单元格时候,就直接展示文本效果(el-select没有值时展示 '请选择', 有值时展示你选择的值)

例图如下:

 滚动区间分页

 不管你有多少条数据,我始终首次只加载这么十多个tr,tr数量不变,变化的是每次的区间取值,大家会发现下面有个tr id为 virtual-scroll,那这是干什么 的呢?其实核心思路是:

显示区的高度 + 已经滚动过的高度 + 虚拟滚动条高度 === 总的数据高度

table-wrapper.clientHeight + table-wrapper.scrollTop + virtual-scroll.height === data.lentth * tr.clientHeight

 是不是发现和方案1类似,但是区别不同的是,虚拟条高度可是一来就会被计算出来的,因为开始时候scrollTop为0, 那么 虚拟条高度 就是 总的数据高度 - 显示区高度

tsx: 通过自定义指令来监听表格的滚动,返回值触发loadMore方法,来决定显示区展示数据的区间

setup() {
    return () => (
        <el-table
            data={visibleResult.value} // 可视区域的数据
            {...{ directives: [{ name: 'load-more', value: methods.loadMore }] }}
        >
        </el-table>
    )
}

ts: 逻辑

  /** 表格上展示的数据 */
  const visibleResult = computed(() => {
    return result.value.filter((_item, index) => {
      if (index < curStartIndex.value) {
        return false;
      } else if (index > curEndIndex.value) {
        return false;
      } else {
        return true;
      }
    });
  })

const methods = {
    /**
     * 懒加载回调
     * @param startIndex 区段位置开始索引
     * @param endIndex 区段位置结束索引
     */
    loadMore(startIndex: number, endIndex: number) {
      curStartIndex.value = startIndex
      curEndIndex.value = endIndex
    },
}

那上面是实现滚动区间的代码,那说的表格编辑相关在哪里呢?下面即是:

表格编辑

当我们表格中有很多下拉框和输入框时,其实拖垮性能的多半是el-select,那解决方法是将渲染input和select的逻辑提出来,input不做处理,select当前单元格row,column索引和focusCell的值一致时说明单元格被聚焦了,就显示el-select,否则直接展示文本

关键方法及属性讲解:

inputChange:输入框回调

selectChange:下拉框选择回调

selectPerofrmance:下拉框当前值渲染

domPropsInnerHTML: 等同于v-html,但是jsx中不能用v-html

decorateHeader:表格各列数据我都是动态配置的,后续我会出一期博客来讲解vue3+tsx下如何封装表格

然后效果大致如下,总结下就是同一时间最多只会存在一个el-select,既然el-select dom减少了,那么表格渲染速度就自然而然快了

 上面例图中被黄色圈中的就是聚焦,没有被聚焦的都展示文本,源码讲解如下:

const focusCell = ref<string>('0,0') // 表格数据第0行第0列

 你在el-table 里有 cell-click 这个事件,它会将当前row, column全都回调回去,那么你就会知道你当前点击单元格的索引值, 我们将索引值生成, 在同一时间,focusCell只会有一条数据,所以我们直接用字符串来存储就好

<el-table
  on-cell-click={methods.cellClick}
>
</el-table>

const methods = {
  cellClick(row, column) {
    if (focusCell.value !== `${row.index},${column.index}`) {
        focusCell.value = `${row.index},${column.index}`
      }
  }
}

那么关键来了,下拉框单元格聚焦的时候,我们才显示下拉框,其他时候展示文本,输入框类型单元格聚焦的时候不做处理,代码该如何写呢?

setup() {
  return () => {
    /** 输入框类型渲染 */
    const inputDomRender = (scope, item) => (
      <el-input
        value={scope.row[item.prop]}
        on-input={e => methods.inputChange(e, scope, item)}
      />
    )
    /** 下拉框类型渲染 */
    const selectDomRender = (scope, item) => (
      (focusCell.value === `${scope.row.autoIndex},${scope.column.index}` ? <el-select
        value-key='id'
        value={scope.row[item.prop]}
        onChange={e => methods.selectChange(e, scope, item)}
      >
        {
          item.selects.map(item1 => {
            return <el-option
              key={item1.id}
              label={item1.label}
              value={item1.id}
            >
            </el-option>
          })
        }
      </el-select> : <div
        domPropsInnerHTML={methods.selectPerofrmance(scope.row[item.prop], item.prop)}></div>)
    )
    return <el-table
      on-cell-click={methods.cellClick}
    >
    {
      decorateHeader.map((item: TableLabel) => {
        return <el-table-column
          width={item.width}
          label={item.label}
          align={item.align ? item.align : 'center'}
          prop={item.prop}
          scopedSlots={{
            default: scope => {
              return <div
              >
                {
                  item.mode !== 'input' ? selectDomRender(scope, item) : inputDomRender(scope, item)
                }
              </div>
            }
          }}
        >
        </el-table-column>
      })
    }
    </el-table>
  }
}

自此上述两步优化,其实就能使我们一次性加载几千条可编辑数据不会卡顿了~

3、v-load-more自定义指令

源码:

import {
  VNodeDirective
} from 'vue'
let timeout;
/** 设置表格滚动区间 */
const setRowScrollArea = (topNum, showRowNum, binding) => {
  if (timeout) {
    clearTimeout(timeout);
  }
  timeout = setTimeout(() => {
    binding.value.call(null, topNum, topNum + showRowNum);
  });
};
const loadMore= {
  bind(el: Element, _binding) {
    setTimeout(() => {
      // 创建虚拟滚动条
      const selectWrap = el.querySelector('.el-table__body-wrapper');
      const selectTbody = selectWrap.querySelector('table tbody');
      const createElementTR = document.createElement('tr');
      createElementTR.id = 'virtual-scroll'
      selectTbody.append(createElementTR); // 先行将虚拟滚动条加入进来
    })
  },
  componentUpdated(el: Element, binding: VNodeDirective, vnode, oldVnode) {
    setTimeout(() => {
      const dataSize = vnode.data.attrs['data-size'];
      const oldDataSize = oldVnode.data.attrs['data-size'];
      // 当数量相同时,表明当前未发生更新,减少后续操作
      if (dataSize === oldDataSize) {
        return;
      }
      const selectWrap = el.querySelector('.el-table__body-wrapper');
      const selectTbody = selectWrap.querySelector('table tbody');
      const selectRow = selectWrap.querySelector('table tr');
      // 当一行都没有,说明无数据渲染,但一般逻辑都不会进入这里
      if (!selectRow) {
        return;
      }
      const rowHeight = selectRow.clientHeight;
      // 能够在当前显示区的展示条数,本项目就是11条
      const showRowNum = Math.round(selectWrap.clientHeight / rowHeight);
      const createElementTRHeight = (dataSize - showRowNum) * rowHeight;
      const createElementTR = selectTbody.querySelector('#virtual-scroll')
      // 监听滚动后事件
      selectWrap.addEventListener('scroll', function() {
        let topPx = this.scrollTop;
        let topNum = Math.round(topPx / rowHeight);
        const minTopNum = dataSize - showRowNum;
        if (topNum > minTopNum) {
          topNum = minTopNum;
        }
        if (topNum < 0) {
          topNum = 0;
          topPx = 0;
        }
        selectTbody.setAttribute('style', `transform: translateY(${topPx}px)`);
        // 本来触底的话,应该设置为0,但是触底后 就没有滚动条了
        createElementTR.setAttribute('style', `height: ${createElementTRHeight - topPx > 0 ? createElementTRHeight - topPx : rowHeight}px;`);
        setRowScrollArea(topNum, showRowNum, binding);
      })
    });
  }
}

export default loadMore

 不太了解自定义指令是啥的可以参考我另一篇博客

vue学习(6)自定义指令详解及常见自定义指令

4、有趣拓展

1、我想在上述表格中对指定列实现高亮搜索怎么做?

当有值时滚动到指定位置,无值时不动,那首先在加载数据时,要先写下面代码

 // 先让他触发滚动,才能让virtual-scroll高度生成
table.$el.querySelector('.el-table__body-wrapper').scrollTo({ top: 1, behavior: 'smooth' })

输入框执行逻辑如下,防抖肯定是要的,然后搜索的列是 originName,当发现有搜索到值时,找到第一个被匹配到的行索引,然后去计算表格应该滚动到哪个高度位置,然后滚动

    /**
     * 滚动定位到表格指定位置
     * @param flag 是否能执行滚动的标志
     * @returns
     */
    scrollToTable: () => $debounce(function() {
      // 当没有值时,不进行搜索
      if (dialogSearchKey.value) {
        const vmEl = table.value.$el;
        const selectWrap = vmEl.querySelector('.el-table__body-wrapper')
        if (vmEl) {
          const autoIndex = result.value.find((item: TableDataItem) => {
            return item.originName.indexOf(dialogSearchKey.value) !== -1
          })?.autoIndex ?? -1
          if (autoIndex !== -1) {
            scrollToIndex(selectWrap, autoIndex)
          }
        }
      }
    }, 500, false),
    /**
     * 表格滚动到指定索引值行
     * @param selectWrap 表格dom
     * @param autoIndex 索引
     */
    scrollToIndex(selectWrap, autoIndex) {
      const showNum = 12 // 当显示条数小于次数时,不进行滚动操作
      const topPx = autoIndex * columnHeight.value
      if (autoIndex > showNum) {
        selectWrap.scrollTo({ top: topPx, behavior: 'smooth' })
      }
    }

 然后高亮被搜索文字源码如下:

setup() {
  return () => {
    return <el-table
      on-cell-click={methods.cellClick}
    >
    {
      decorateHeader.map((item: TableLabel) => {
        return <el-table-column
          width={item.width}
          label={item.label}
          align={item.align ? item.align : 'center'}
          prop={item.prop}
          scopedSlots={{
            default: scope => {
              return <div
              >
                <div
                  class='multiline'
                  domPropsInnerHTML={methods.textRender(scope.row[item.prop], item.prop)}
                />
              </div>
            }
          }}
        >
        </el-table-column>
      })
    }
    </el-table>
  }
}

 props.heightLight  为输入框的搜索关键词

    /**
     * 文字渲染
     * @param word 被渲染的文字
     * @param prop 属性名
     * @returns 替换后的渲染文字
     */
    textRender(word: string, prop: string): string {
      const reg = new RegExp(`${props.heightLight}`, 'ig')
      // 有搜索关键词 && 是否有该子字符串
      if (props.heightLight && word.indexOf(props.heightLight) !== -1) {
        return word.replace(reg, `<font color='red'>$&</font>`)
      // 正常返回的列
      } else {
        return word
      }
    }

---本篇还是相当实用,喜欢就一键三连吧~欢迎评论---

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/546258.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows10 双网卡配置,轻松实现内外互通

1 背景 我平常需要连接外网查阅资料&#xff0c;聊天等&#xff0c;做实验时需要将写到的代码打包部署到内网服务器中&#xff0c;所以需要频繁地切换内外网&#xff0c;修改静态网络配置。很是苦恼。 2 方法 最近&#xff0c;我找到了几种解决办法。 方法一、…

uniapp DIY可视化工具 控件拖拽工具怎么开发/拖拽库/模板引擎

uniapp DIY可视化工具 控件拖拽工具怎么开发 答&#xff1a;需要 (**拖拽库**) &#xff08;**模板引擎**&#xff09;代码自动生成 python有哪些模板引擎 Jinja2是一个小型但快速且易于使用的以纯python编写的独立模板引擎&#xff08;最主要的模板&#xff09;Chameleon是T…

深度学习之卷积神经网络(CNN)

大家好&#xff0c;我是带我去滑雪&#xff01; 卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;是一种基于深度学习的前馈神经网络&#xff0c;主要用于图像和视频识别、分类、分割和标注等计算机视觉任务。它主要由卷积层、池化层、全连接…

【开源、应用】QT—TCP网络上位机的设计

本文设计一个终端控制的上位机软件&#xff08;如“设计目标”下图所示&#xff09;&#xff0c;可以和STM32、Adruino等通信实现无线局域网控制系统。 本文的通信内容和图表内容可以参考作者之前的文章 STM32ESP8266连接电脑Qt网络上位机——QT篇https://blog.csdn.net/qq_53…

【STM32G431RBTx】备战蓝桥杯嵌入式→决赛试题→第六届

文章目录 前言一、题目二、模块初始化三、代码实现interrupt.h:interrupt.c:main.h:main.c: 四、完成效果五、总结 前言 无 一、题目 二、模块初始化 1.LCD这里不用配置&#xff0c;直接使用提供的资源包就行 2.ADC:开启ADCsingle-ended 3.LED:开启PC8-15,PD2输出模式就行了…

【JVM001】宋红康JVM字节码举例

宋红康JVM字节码举例 1 Integer package jvmT; public class IntegerTest {public static void main(String[] args) {Integer i 5;int y 5;System.out.println(iy); //trueInteger i6 5;Integer y6 5;System.out.println(i6y6);//trueInteger i5 128;Integer y5 128;System.…

SpringBoot中使用lombok

1.添加依赖 在项目的根目录中找到pom.xml&#xff0c;在dependencies下复制这段代码 <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifac…

解决Python爬虫中selenium模块中的find_element_by_id方法无法使用

如有错误&#xff0c;敬请谅解&#xff01; 此文章仅为本人学习笔记&#xff0c;仅供参考&#xff0c;如有冒犯&#xff0c;请联系作者删除&#xff01;&#xff01; 我们在学习selenium模块的时候&#xff0c;经常会用到 browser.find_element_by_id命令&#xff0c;但随着se…

代码随想录算法训练营第四十六天 | bool的背包题,细节多

139.单词拆分 文档讲解&#xff1a;代码随想录 (programmercarl.com) 视频讲解&#xff1a;动态规划之完全背包&#xff0c;你的背包如何装满&#xff1f;| LeetCode&#xff1a;139.单词拆分_哔哩哔哩_bilibili 状态&#xff1a;不会做&#xff0c;不知道怎么把bool类型与背包…

推荐系统系列之推荐系统概览(下)

在推荐系统概览的第一讲中&#xff0c;我们介绍了推荐系统的常见概念&#xff0c;常用的评价指标以及首页推荐场景的通用召回策略。本文我们将继续介绍推荐系统概览的其余内容&#xff0c;包括详情页推荐场景中的通用召回策略&#xff0c;排序阶段常用的排序模型&#xff0c;推…

软件测试实验:loadrunner的高级使用

目录 前言实验目的实验内容实验要求实验过程loadrunner中插入事务与集合点loadrunner中插入检查点loadrunner中参数化-table分析报告功能loadrunner手动设置场景loadrunner监视图标 总结 前言 本实验主要介绍了loadrunner这一强大的性能测试工具的高级使用方法&#xff0c;包括…

python实现九宫格的车辆路径轨迹上位机界面

实验环境&#xff1a;wxFormBuilder v3.5 python3.7.5 MC9S12G128开发板 基本功能&#xff1a;控制开发板上的按键&#xff0c;模拟车辆移动的上下左右四个方位&#xff0c;通过can通信告诉上位机界面&#xff0c;车辆轨迹的移动方位&#xff1b; 1. python重新封装control…

技巧:jetbrain全家桶系列如何撤销已经提交本地仓库但还没push的commit

目录 1. 哎呀&#xff0c;不小心把不能提交的“机密”加入commit了2. 使用reset来修复的话要注意有坑&#xff0c;选Soft和Mixed&#xff0c;千万别选Hard和Keep3. 使用revert&#xff0c;只能修修补补&#xff0c;但commit还在&#xff0c;当然有好处是会留下使用痕迹&#xf…

异常处理机制

编程错误 编写程序时遇到的错误可大致分为 2 类&#xff0c;分别为语法错误和运行时错误。 语法错误 语法错误&#xff0c;也就是解析代码时出现的错误。当代码不符合Python语法规则时&#xff0c;Python解释器在解析时就会报出SyntaxError语法错误&#xff0c;与此同时还会…

服务(第二十六篇)redis的主从复制、哨兵、集群

主从复制&#xff1a; 主从复制&#xff0c;是指将一台Redis服务器的数据&#xff0c;复制到其他的Redis服务器。前者称为主节点(Master)&#xff0c;后者称为从节点(Slave)&#xff1b;数据的复制是单向的&#xff0c;只能由主节点到从节点。 原理&#xff1a; 主从关系确定…

[算法前沿]--009-HuggingFace介绍(大语言模型底座)

基础介绍 HuggingFace 是一家专注于自然语言处理(NLP)、人工智能和分布式系统的创业公司,创立于2016年。最早是主营业务是做闲聊机器人,2018年 Bert 发布之后,他们贡献了一个基于 Pytorch 的 Bert 预训练模型,即 pytorch-pretrained-bert,大受欢迎,进而将重心转向维护…

PoseiSwap以2500万美元估值,再获新一轮融资

近日&#xff0c;Nautilus Chain 上的首个 DEX PoseiSwap 宣布&#xff0c;其目前已经以 2500 万美元的估值&#xff0c;从 Gate Labs、Emurgo Ventures、Republic以及Cipholio Ventures 等行业顶级投资机构中&#xff0c;获得了新一轮的融资&#xff0c;不过目前该融资的具体数…

asp.net网上捐赠系统

一该源码功能十分的全面&#xff0c;具体介绍如下&#xff1a; 本版本软件主要完成三个功能&#xff1a; 1、建立网上捐赠功能 实现网上捐赠程序自动化&#xff0c;智能化&#xff0c;在捐赠者与受捐者填写各种资料后&#xff0c;自动保存方便以后调阅查询&#xff0c…

Java【网络编程1】详解DatagramSocket和DatagramPacket类, 逐行代码解析如何服务器客户端通信(附代码)

文章目录 前言一、认识 Socket(套接字), TCP 协议和 UDP 协议1, 什么是 Socket(套接字)2, 浅谈 TCP 协议和 UDP 协议的区别和特点 二、基于 UDP 协议的 Socket API1, DatagramSocket 类2, DatagramPacket 类 三、逐行代码解析网络编程1, 逐行解析客户端1.1, 核心成员方法 start…

【C++】-类和对象完结(内部类、匿名对象以及编译器的优化的讲解)(下)

&#x1f496;作者&#xff1a;小树苗渴望变成参天大树 ❤️‍&#x1fa79;作者宣言&#xff1a;认真写好每一篇博客 &#x1f4a8;作者gitee:gitee &#x1f49e;作者专栏&#xff1a;C语言,数据结构初阶,Linux,C 如 果 你 喜 欢 作 者 的 文 章 &#xff0c;就 给 作 者 点 …