当表格数据量过大的时候,如何使用不分页进行展示

表格数据过大的时候,渲染时间则增大。使用分页就可以解决,但是有的时候用户不想使用分页,那么前端可以监听滚动条进行懒加载,但是当数据都加载出来。数据量就又大了。

  1. 组件文件名: elTableVirtualScroll.vue 通过仅渲染可视区域内的内容来减少 DOM 节点数量,从而提升页面响应速度和内存使用效率

    <template>
    <slot></slot>
    </template> <script> import throttle from 'lodash/throttle'

    export default {
    name: 'el-table-virtual-scroll',
    props: {
    data: {
    type: Array,
    required: true
    },
    height: {
    type: Number,
    default: 60
    },
    buffer: {
    type: Number,
    default: 500
    },
    keyProp: {
    type: String,
    default: 'id'
    },
    throttleTime: {
    type: Number,
    default: 100
    }
    },
    data () {
    return {
    sizes: {} // 尺寸映射(依赖响应式)
    }
    },
    computed: {
    // 计算出每个item(的key值)到滚动容器顶部的距离
    offsetMap ({ keyProp, height, sizes, data }) {
    const res = {}
    let total = 0
    for (let i = 0; i < data.length; i++) {
    const key = data[i][keyProp]
    res[key] = total

    复制代码
         const curSize = sizes[key]
         const size = typeof curSize === 'number' ? curSize : height
         total += size
       }
       return res
     }

    },
    methods: {
    // 初始化数据
    initData () {
    // 可视范围内显示数据
    this.renderData = []
    // 页面可视范围顶端、底部
    this.top = undefined
    this.bottom = undefined
    // 截取页面可视范围内显示数据的开始和结尾索引
    this.start = 0
    this.end = undefined

    复制代码
       this.scroller = this.$el.querySelector('.el-table__body-wrapper')
    
       // 初次执行
       setTimeout(() => {
         this.handleScroll()
       }, 100)
    
       // 监听事件
       this.onScroll = throttle(this.handleScroll, this.throttleTime)
       this.scroller.addEventListener('scroll', this.handleScroll)
       window.addEventListener('resize', this.onScroll)
     },
    
     // 更新尺寸(高度)
     updateSizes () {
       const rows = this.$el.querySelectorAll('.el-table__body > tbody > .el-table__row')
    
       Array.from(rows).forEach((row, index) => {
         const item = this.renderData[index]
         if (!item) return
    
         const key = item[this.keyProp]
         const offsetHeight = row.offsetHeight
    
         if (this.sizes[key] !== offsetHeight) {
           this.$set(this.sizes, key, offsetHeight)
         }
       })
     },
    
     // 处理滚动事件
     handleScroll (shouldUpdate = true) {
       // 更新当前尺寸(高度)
       this.updateSizes()
       // 计算renderData
       this.calcRenderData()
       // 计算位置
       this.calcPosition()
       shouldUpdate && this.updatePosition()
       // 触发事件
       this.$emit('change', this.renderData, this.start, this.end)
     },
    
     // 获取某条数据offsetTop
     getOffsetTop (index) {
       const item = this.data[index]
       if (item) {
         return this.offsetMap[item[this.keyProp]] || 0
       }
       return 0
     },
    
     // 获取某条数据的尺寸
     getSize (index) {
       const item = this.data[index]
       if (item) {
         const key = item[this.keyProp]
         return this.sizes[key] || this.height
       }
       return this.height
     },
    
     // 计算只在视图上渲染的数据
     calcRenderData () {
       const { scroller, data, buffer } = this
       // 计算可视范围顶部、底部
       const top = scroller.scrollTop - buffer
       const bottom = scroller.scrollTop + scroller.offsetHeight + buffer
    
       // 二分法计算可视范围内的开始的第一个内容
       let l = 0
       let r = data.length - 1
       let mid = 0
       while (l <= r) {
         mid = Math.floor((l + r) / 2)
         const midVal = this.getOffsetTop(mid)
         if (midVal < top) {
           const midNextVal = this.getOffsetTop(mid + 1)
           if (midNextVal > top) break
           l = mid + 1
         } else {
           r = mid - 1
         }
       }
    
       // 计算渲染内容的开始、结束索引
       let start = mid
       let end = data.length - 1
       for (let i = start + 1; i < data.length; i++) {
         const offsetTop = this.getOffsetTop(i)
         if (offsetTop >= bottom) {
           end = i
           break
         }
       }
    
       // 开始索引始终保持偶数,如果为奇数,则加1使其保持偶数【确保表格行的偶数数一致,不会导致斑马纹乱序显示】
       if (start % 2) {
         start = start - 1
       }
       // console.log(start, end, 'start end')
    
       this.top = top
       this.bottom = bottom
       this.start = start
       this.end = end
       this.renderData = data.slice(start, end + 1)
     },
    
     // 计算位置
     calcPosition () {
       const last = this.data.length - 1
       // 计算内容总高度
       const wrapHeight = this.getOffsetTop(last) + this.getSize(last)
       // 计算当前滚动位置需要撑起的高度
       const offsetTop = this.getOffsetTop(this.start)
    
       // 设置dom位置
       const classNames = ['.el-table__body-wrapper', '.el-table__fixed-right .el-table__fixed-body-wrapper', '.el-table__fixed .el-table__fixed-body-wrapper']
       classNames.forEach(className => {
         const el = this.$el.querySelector(className)
         if (!el) return
    
         // 创建wrapEl、innerEl
         if (!el.wrapEl) {
           const wrapEl = document.createElement('div')
           const innerEl = document.createElement('div')
           wrapEl.appendChild(innerEl)
           innerEl.appendChild(el.children[0])
           el.insertBefore(wrapEl, el.firstChild)
           el.wrapEl = wrapEl
           el.innerEl = innerEl
         }
    
         if (el.wrapEl) {
           // 设置高度
           el.wrapEl.style.height = wrapHeight + 'px'
           // 设置transform撑起高度
           el.innerEl.style.transform = `translateY(${offsetTop}px)`
           // 设置paddingTop撑起高度
           // el.innerEl.style.paddingTop = `${offsetTop}px`
         }
       })
     },
    
     // 空闲时更新位置
     updatePosition () {
       this.timer && clearTimeout(this.timer)
       this.timer = setTimeout(() => {
         this.timer && clearTimeout(this.timer)
         // 传入false,避免一直循环调用
         this.handleScroll(false)
       }, this.throttleTime + 10)
     },
    
     // 【外部调用】更新
     update () {
       console.log('update')
       this.handleScroll()
     },
    
     // 【外部调用】滚动到第几行
     scrollTo (index, stop = false) {
       const item = this.data[index]
       if (item && this.scroller) {
         this.updateSizes()
         this.calcRenderData()
    
         this.$nextTick(() => {
           const offsetTop = this.getOffsetTop(index)
           this.scroller.scrollTop = offsetTop
    
           // 调用两次scrollTo,第一次滚动时,如果表格行初次渲染高度发生变化时,会导致滚动位置有偏差,此时需要第二次执行滚动,确保滚动位置无误
           if (!stop) {
             setTimeout(() => {
               this.scrollTo(index, true)
             }, 50)
           }
         })
       }
     },
    
     // 【外部调用】重置
     reset () {
       this.sizes = {}
       this.scrollTo(0, false)
     }

    },
    watch: {
    data () {
    this.update()
    }
    },
    created () {
    this.$nextTick(() => {
    this.initData()
    })
    },
    beforeDestroy () {
    if (this.scroller) {
    this.scroller.removeEventListener('scroll', this.onScroll)
    window.removeEventListener('resize', this.onScroll)
    }
    }
    }
    </script>

    <style lang='less' scoped> </style>
  2. 使用案例:在文件中引入组件,tableData1变量是表格全部数据,tableData则是展示的数据,tableDataChange用来监听容器滚动条,进而计算展示数据

    复制代码
               <VirtualScroll 
                 :data="tableData1"
                 :height="62"
                 key-prop="id"
                 @change="tableDataChange">
                 <el-table
                   :data="tableData"
                   border
                   ref="tables"
                   height="50vh"
                   v-loading="tableLoading"
                   style="width: 100%;">
                   
                 </el-table>
               </VirtualScroll>

    tableDataChange(renderData) {
    this.tableData = renderData
    if(this.tableData.length > 0) {
    this.tableData.unshift(this.formObject)
    }
    },

相关推荐
JosieBook2 小时前
【Vue】03 Vue技术——Vue.js 入门学习笔记:Hello World 案例详解
vue.js·笔记·学习
How_doyou_do2 小时前
常见的设计模式
前端·javascript·设计模式
Gomiko3 小时前
JavaScript DOM 原生部分(五):事件绑定
开发语言·前端·javascript
巴拉巴拉~~3 小时前
Flutter 通用下拉选择组件 CommonDropdown:单选 + 搜索 + 自定义样式
开发语言·javascript·ecmascript
Можно3 小时前
深入理解 JavaScript 函数:分类、特性与实战应用
开发语言·javascript
lionliu05193 小时前
JavaScript 变量声明最佳实践
前端·javascript·vue.js
计算机毕设VX:Fegn08953 小时前
计算机毕业设计|基于springboot + vue酒店管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
AAA阿giao3 小时前
从零开始学 React:用搭积木的方式构建你的第一个网页!
前端·javascript·学习·react.js·前端框架·vite·jsx
2503_928411564 小时前
12.15 element-plus的一些组件(上)
前端·vue.js