【项目数据优化三】长列表数据优化

简介: 【项目数据优化三】长列表数据优化

数字化管理平台

Vue3+Vite+VueRouter+Pinia+Axios+ElementPlus

Vue权限系统案例

个人博客地址

前言:

在移动端项目中,我们通常会使用懒加载来加载列表,优点很明显,不需要一次将数据请求完,当用户下拉到底部时,才使用ajax动态从服务器拉取接下来的数据。但是这又导致了一个问题,如果用户疯狂进行下拉呢,这就会导致浏览器创建多个多余的节点,出现冗余,并且你拥有多少个节点,vue就会diff多少个节点,这样的场景会带来多余的性能消耗和内存占用。试想一下,如果我们能只渲染用户可视区域的节点,便能很好的解决这个问题,这便是虚拟滚动的产生背景。

虚拟滚动原理:

首先我们要知道,虚拟滚动是用到 Vue 的 v-for 实现的,上面也解释了,虚拟滚动是只渲染可视区域,那么我们可视区域的节点内容必然会随着用户的滚动条的改变而改变,假设一个页面就只能显示 n 个节点,那么我们如何让这n个需要变动的节点跟着滚动条动呢?

使用CSStransform:translateY(),这样,我们只需要让这n个节点跟着滚动条移动,我们滚动到哪里,节点替换到哪里。

要实现虚拟滚动你只需要知道以下条件:

  1. 1. 一个页面能显示多少个item?

页面容量 = 页面大小(clientHeight)/单个item大小

showNum = Math.floor(viewH / itemH) + 4 # 这里多设置几个防止滚动时候直接替换,所以 +4

  1. 2. 应该从哪个节点开始渲染?

我们假设现在滚动条滚动到了x的位置,我们是否就可以计算出这个x这个高度可以容纳多少个节点,进而得出应该从哪个节点开始渲染呢?答案是肯定的,js为我们提供了scrollTop这个属性来获取滚动条卷入的高度。

 getCurStart(scrollTop){
  // 卷去了多少个
  return Math.floor(scrollTop/(itemHeight));
}
  1. 3. 什么时候渲染?

渲染时机也很简单,列表中第一个节点被完全卷入的时候我们需要执行渲染,因为这个时候被完全卷去的节点已经完全看不到了,我们需要将它顶下来然后再将其渲染成下一项的数据,这么说可能有点难理解,放张图吧~

800c5f7edc4b4b61b29af55fae030ca7.png

如上图,当1被卷去的时候(完全离开我们的可视区),我们就利用css的translateY将它顶下来,渲染成2,你会发现发现在可视区域外多出来了一个节点,为了保证滑动的连贯性,你可以多设置几个冗余节点。

  1. 4. 如何渲染

这部分就是最核心的代码了。在这会出现一个问题,由于js并不是对每次都会对高频触发的回调进行响应,你如果不获取一个可以被itemHeight整除的偏移量,你极有可能拉回去的时候看到第一个节点的偏移量并不是0。

onScroll(){
  //scrollTop常量记录当前滚动的高度
  const scrollTop=this.$refs.list.scrollTop;
  const start=this.getCurStart(scrollTop);
  //对比上一次的开始节点 比较是否发生变化,发生变化后便重新渲染列表
  if(this.start!=start){
    //在这需要获得一个可以被itemHeight整除的数来作为item的偏移量
    const offsetY = scrollTop - (scrollTop % this.itemHeight);
    //使用slice拿到需要渲染的那一部分
    this.renderList=this.list.slice(start,this.start+this.showNum);
    //这里的top用于设置translateY  transform:translateY(${top}px)
    this.top=offsetY;
  }
  this.start=start;
}
  1. 5. 优化处理

onScroll属高频触发回调,为了节约性能消耗,我们需对其加以限制,让其最短50ms触发一次。以下是封装节流函数。

# throttle.js 
export default function(fn, delay) {
    let lock = false;
    return (...args) => {
        if (lock)
            return;
        //进入加锁
        lock = true;
        setTimeout(() => {
            fn.apply(this, args);
            //执行完毕解锁
            lock = false;
        }, delay);
    }
}

页面中滚动完整代码(Vue2 Options API):

<template>
    <div class="list" @scroll="scrollHandle" ref="list">
        <div class="item" v-for="(item,index) in renderList" :key="index"  :style="`height:${itemHeight}px;line-height:${itemHeight}px;transform:translateY(${top}px)`">
          {{item}}
        </div>
    </div>
</template>
<script>
import throttle from '@/utils/throttle';
export default {
  name: 'App',
  data() {
    return {
      list:[],//完整列表
      itemHeight:60,//每一项的高度
      renderList:[],//需要渲染的列表
      start:0,//开始渲染的位置
      showNum:0,//页面的容积:能装下多少个节点
      top:0,
      scroll,//用于初始化节流
    }
  },
  mounted() {
    this.initList();
    const cHeight=document.documentElement.clientHeight
    //计算页面能容纳下几个节点并且设置四个节点作为冗余
    this.showNum=Math.floor(cHeight/this.itemHeight)+4;
    //设置要渲染的列表 设置成能够容纳下的最大元素个数
    this.renderList=this.list.slice(0,this.showNum);
    //初始化节流函数 最短50毫秒触发一次
    this.scroll=throttle(this.onScroll,50);
  },
  methods: {
    //初始化列表 ,循环渲染 500条
    initList(){
      for(let i=0;i<500;i++){
        this.list.push(i);
      }
    },
    scrollHandle(){
      this.scroll();
    },
    onScroll(){
      //scrollTop常量记录当前滚动的高度
      const scrollTop=this.$refs.list.scrollTop;
      const start=this.getCurStart(scrollTop);
      //对比上一次的开始节点 比较是否发生变化,发生变化后便重新渲染列表
      if(this.start!=start){
        //在这需要获得一个可以被itemHeight整除的数来作为item的偏移量
        const offsetY = scrollTop - (scrollTop % this.itemHeight);
        //使用slice拿到需要渲染的那一部分
        this.renderList=this.list.slice(start,this.start+this.showNum);
        //这里的top用于设置translateY  transform:translateY(${top}px)
        this.top=offsetY;
      }
      this.start=start;
    },
    getCurStart(scrollTop){
      //卷去了多少个
      return Math.floor(scrollTop/(this.itemHeight));
    }
  },
}
</script>
<style>
    *{
      margin: 0;
      padding: 0;
    }
    .list{
      height: 100vh;
      overflow: scroll;
    }
    .item{
      text-align: center;
      width: 100%;
      box-sizing: border-box;
      border-bottom: 1px solid lightgray;
    }
</style>

容器中滚动代码(Vue3 Composition API)

<script setup>
    /**
     * 项目列表数据越来越多(上万条),正常列表可以分页,但是像下拉框之类组件就不能分页。每次都要加载所有的(很慢),性能不好的浏览器特别卡顿。虚拟滚动的技术完美解决。
     * 主要用于无法使用分页功能的长列表首屏加载速度慢问题,DOM加载过多“无用”元素。
     * 核心:
     *      1. 元素监听scroll事件
     *      2. 计算可视化高度一次能装几个列表,然后从总数据中进行slice截取
     *      3. 每一次滚动后根据scrollTop值获取一个可以整除itemH结果进行偏移
     */
    import { onMounted, reactive, ref } from 'vue';
    const listEle = ref()
    // 上万条总数居
    // const list = reactive(Array.apply(null, { length: 100000 }).map((v, i) => i))
    const list = reactive(Array.from({ length: 100000 }).map((_, i) => {
        return {
            key: i,
            value: i + 1
        }
    }))
    // 页面高度
    const viewH = 800
    // 单项高度
    let itemH = 200
    // 整个滚动列表高度
    let scrollH = itemH * list.length
    // 可视化高度一次可装列表数量(多设置几个防止滚动时候直接替换)
    let showNum = Math.floor(viewH / itemH) + 4
    // 页面上展示的数据
    let showList = reactive(list.slice(0, showNum))
    // 动态偏移量
    let offsetY = ref(0)
    // 时间戳
    let latestTime = new Date().getTime()
    onMounted(() => {
    })
    let timer = ref(null)
    const handleScroll = (e) => {
        if (new Date().getTime() - latestTime > 10) {
            clearInterval(timer.value)
            timer.value = setTimeout(() => {
                // 获取卷去的高度
                let scrollTop = e.target.scrollTop
                // 每一次滚动后,根据卷去高度 scrollTop 值,获取一个可以整除单项高度 itemH 的结果进行偏移
                // 例如: 卷去的 scrllTop = 1020  1020 % itemH = 20  offsetY = 1000
                offsetY.value = scrollTop - (scrollTop % itemH)
                // 更新数据:被卷去几条数据,就要往下增加几条数据
                showList = list.slice(Math.floor(offsetY.value / itemH), Math.floor(offsetY.value / itemH) + showNum)
                // 更新时间戳
                latestTime = new Date().getTime()
                console.log(showList)
            }, 300);
        }
    }
</script>
<template>
    <div :style="`height:${viewH}px;overflow-y:scroll;`" @scroll="handleScroll">
        <ul ref="listEle">
            <li v-for="item in showList" :key="item.key"
                :style="{ 'transform': `translateY(${offsetY}px)`, 'height': `${itemH}px`, 'line-height': `${itemH}px` }">{{
                    item.value }}</li>
        </ul>
    </div>
</template>
<style scoped>
    li {
        /* height: 200px;
        line-height: 200px; */
        color: #fff;
        font-size: 50px;
        font-weight: bold;
        text-align: center;
        background-color: orange;
    }
    li:nth-of-type(odd) {
        background-color: blue;
    }
</style>


相关文章
|
1月前
|
存储 监控 测试技术
|
6月前
|
分布式计算 关系型数据库 数据挖掘
实时数仓 Hologres产品使用合集之当使用动态分区管理功能按日期进行分区后,通过主键和segment_key进行时间范围查询性能变差是什么原因
实时数仓Hologres的基本概念和特点:1.一站式实时数仓引擎:Hologres集成了数据仓库、在线分析处理(OLAP)和在线服务(Serving)能力于一体,适合实时数据分析和决策支持场景。2.兼容PostgreSQL协议:Hologres支持标准SQL(兼容PostgreSQL协议和语法),使得迁移和集成变得简单。3.海量数据处理能力:能够处理PB级数据的多维分析和即席查询,支持高并发低延迟查询。4.实时性:支持数据的实时写入、实时更新和实时分析,满足对数据新鲜度要求高的业务场景。5.与大数据生态集成:与MaxCompute、Flink、DataWorks等阿里云产品深度融合,提供离在线
|
7月前
|
缓存 关系型数据库 MySQL
MySQL查询优化:提速查询效率的13大秘籍(合理使用索引合并、优化配置参数、使用分区优化性能、避免不必要的排序和group by操作)(下)
MySQL查询优化:提速查询效率的13大秘籍(合理使用索引合并、优化配置参数、使用分区优化性能、避免不必要的排序和group by操作)(下)
325 0
|
7月前
|
缓存 关系型数据库 MySQL
MySQL 查询优化:提速查询效率的13大秘籍(索引设计、查询优化、缓存策略、子查询优化以及定期表分析和优化)(中)
MySQL 查询优化:提速查询效率的13大秘籍(索引设计、查询优化、缓存策略、子查询优化以及定期表分析和优化)(中)
1153 0
|
7月前
|
存储 分布式数据库 Apache
记录级别索引:Apache Hudi 针对大型数据集的超快索引
记录级别索引:Apache Hudi 针对大型数据集的超快索引
78 2
|
SQL 关系型数据库 MySQL
案例27-单表从9个更新语句调整为2个
单表从9个更新语句调整为2个
102 0
【SQL开发实战技巧】系列(七):从有重复数据前提下如何比较出两个表中的差异数据及对应条数聊起
本篇文章讲解的主要内容是:***如果有重复数据如何检查出两个表中的差异数据及对应条数、表连接做聚合容易出现重复计算的错误、多表查询空值处理问题、NOT IN的子查询范围不能是空值,否则查询结果为空。***
【SQL开发实战技巧】系列(七):从有重复数据前提下如何比较出两个表中的差异数据及对应条数聊起
|
SQL 关系型数据库 MySQL
索引单表优化案例|学习笔记
快速学习索引单表优化案例
128 0
|
SQL 缓存 监控
列表查询的通用优化方案
> 列表查询是服务端开发中非常高频的诉求,接口的性能往往会跟用户体验强关联。本文通过一个具体的例子,来总结服务端写查询接口时的通用优化方案。 ## 一个例子 ### 功能诉求 给出一个具体的例子,背景是根据内容ID来查询内容信息(如下),目标是通过编码优化使得这个查询效率变快,减少上游(客户端App或外部服务)的等待时间。 ```java public interfa
1367 2
列表查询的通用优化方案
|
存储 缓存 分布式计算
指定表和分区来预先缓存,查询分析更高效 | 学习笔记
快速学习指定表和分区来预先缓存,查询分析更高效。
160 0