表格中加入大量输入框导致页面卡顿--优化记录

3,592 阅读4分钟

最近项目有个优化需求--在表格中加入多个数字型输入框,输入框中还有计算逻辑,且每个单元格中的数据计算逻辑都是有关联的, 如下截图表格(表格列数据很多,只是截取了一部分)可清晰解释需求:A-H下面的都代表不同的字段名,比如A列中A1-A5是不同的字段名,以此类推。

image.png

由于表格中的每个单元格数据每个字段都是不同的,首先想到的实现

第一个方案就是直接使用el-table实现,但是发现表格中的行和列中的每个单元格的字段名都是不一样的,而el-table中每列的字段名prop都是一样的,单纯用el-table实现是不行的。

第二个方案就是直接用原生的div,再通过css的布局实现,但是这个方案首先确实很low,其次是页面写起来很繁琐,还得单独对css的的样式布局。表格的还是多级表头。总之很low很繁琐。

能力有限只能想到这两种方案了,第二种直接放弃,

分析了表格中的数据,表格的行和列的每个单元格的字段名是不一样的,单纯使用一个el-table是实现不了的,那就尝试用多个el-table组合起来实现。

基本实现思路是:表格包括表头一共有七行,拆分表格为6个el-table,表头和第二行的字母为一个table,这样每个table的每列的字段都是唯一的。

单独抽出Table-column到一个js文件

image.png

再引入到组件中

image.png

组件中添加6个el-table,在通过for循环遍历table-column,

image.png

table中的columns的文本为输入框el-input,同步需要对input进行输入校验,引入el-form,这样基本页面就完成了,

但是查看页面问题就来了,由于表格是在el-dialog弹框中,每次点击弹框的时候页面反应卡顿迟钝,cpu占比瞬间达到100%,同时在表格中输入数据时页面反应也卡顿,同时各个单元格中的数据还有关联关系,比如汇总相加等,出现的问题有如下:

  1. 弹框页面反应迟钝,
  2. 输入框显示卡顿,
  3. 关联单元格数据未展示,(M1=H1-I1-I2-I3)其他数据输入后M1数据没显示,还是默认0.
  4. 6个表格的滚动条均展示,(只需要最后一个表格的滚动条,且其他表格需与最后一个表格同步滚动)
  5. 表格滚动卡顿。

以上问题的1、2、5,卡顿问题就是由于表格中大量input输入框导致的,这是因为每个 input 输入框都会触发 Vue 的响应式系统,导致频繁的 DOM 更新。为了优化性能,v-model.lazy和虚拟滚动、节流都有试过,但是效果不佳,

是由于每次输入input都会触发dom的更新,且各个输入框的数据是有关联的,这样就导致dom的不断更新,所以页面会卡顿,那可以尝试需要输入框的时候再展示input,不需要的时候展示span,例如下图

image.png

image.png

切换input时,需要加上nextTickdom更新完后再执行focus方法,否则input框还未存在,具体原因就得了解nextTick, dom更新完后再执行focus方法,否则input框还未存在,具体原因就得了解`nextTick`的原理了,

按照此逻辑方法后以上问题的1、2、5就能得到有效的优化解决。

对于第3个问题数据没及时展示的也是有dom获取数据延迟的问题,Vue 是异步更新 DOM 的,同样可以使用$nextTick去解决,nextTick原理就是确保DOM更新后执行操作,具体看一下nextTick原理就是确保 DOM 更新后执行操作,具体看一下nextTick原理:

image.png

剩下的第4个问题就是样式问题了,首先如果要6个表格保持同样的滚动位置,就需要监听对应的table的滚动,并将滚动位置赋值给其他table,如下代码:

image.png

如上代码可以看到也监听了第一个表格的header表头,因为在监听滚动过程中发现第一个表格表头有滚动延迟卡顿和错位的问题,所以就需要同步监听表头。

对于隐藏其他表格的滚动条,在实现样式的过程中有两种方式,

  1. 用overflow:hidden隐藏滚动条
  2. ::-webkit-scrollbar-thumb设置display:none或者设置宽高为0; 第二种方式发现在不同版本的chrome浏览器中出现不生效的情况(暂未找到解决方法

image.png

image.png

更新:**

在watch中监听计算数据变化中使用了大量的nextTick,就是为了实现输入数据后能及时获取到更新后的dom,并更新关联表格数据,但是在频繁数据变化中过度使用nextTick就导致了性能问题,因为每次调用nextTick都会增加微任务的数量,从而增加事件循环的负担,所以只能通过封装方法处理计算逻辑,以减少$nextTick的使用,以下为封装的方法:

roundTODecimal(num,decimalPlaces){
     const factor =Math.pow(10,decimalPlaces)
     return Math.round(num*factor)/factor
     },
 calculateGst(item,fields,deductions=[]){
     let sum =fields.reduce((sum,field)=>sum+Number(item[field]),0)
     deductions.forEach((decution)=>{
     sum-=Number(item[decution])
     })
     return this.roundTODecimal(sum,2)
     },
calculatePaybleGst(item,fields,deductions=[]){
     let sum =fields.reduce((sum,field)=>sum+Number(item[field]),0)
     deductions.forEach((decution)=>{
     sum-=Number(item[decution])
     })
     return Math.max(0,this.roundTODecimal(sum,2))
     },
     
calculateItcCarryForward(item,totalField,deductionsFields){
     let total =Number(item[totalField])
     deductionsFields.forEach((decution)=>{
     total-=Number(item[decution])
     })
     return this.roundTODecimal(total,2)
     },