如果让你渲染十万条数据,无脑全部渲染?拖出去斩了

前言:

何为虚拟列表?

回答这个问题先想一下,对于返回的几千上万条列表数据,作为前端会如何渲染?

分页呗,最开始想到的就是分页。但是,万一接口没做分页呢?好,就算后端做分页了,那么用户在浏览到第几千条、第几万条数据时前端难道要把这第几千条、几万条的DOM都渲染出来吗?显然是很消耗性能的(毕竟如此多的DOM,而且每个DOM内部都有其他样式细节)如何优化?此时就可以用到虚拟列表了

虚拟列表是一种优化长列表渲染的技术,它可以在保持流畅性的同时,渲染大量的数据。

在传统的列表渲染中,如果列表非常长,会导致渲染时间过长(前面所说的会有几千几万个DOM),页面卡顿,用户体验变得非常差。而虚拟列表则是只渲染可见区域内的数据,而非全部渲染,这样就可以大大提高渲染效率,保持页面流畅性

常见场景:

商品列表、社交列表...(暂时想到这两个)

手写虚拟列表:

进行虚拟列表的实现之前先搞清楚一个问题:

是什么造成了这么多数据在渲染时产生卡顿?是数据数量太多引起的吗?

准确来说,应该是是由于要渲染的DOM太多造成的卡顿。数据本身只是数据,对于拿到的几千上万条数据它本身的大小对于内存来说只能说是冰山一角吧

虚拟列表的原理:

数据我还是这么多数据,但是我不一次性渲染这么多数据,我只渲染其中的一小部分(比如十条),这样当用户滚动的时候就重复变化这一小部分的DOM的渲染效果。这样就能从原先几千上万个DOM变成现在的十个,减少了一个量级,减轻了渲染的压力。而这一小部分显示的区域暂称之为视图区域吧

审查元素,大致的效果如图:

贼长的这部分是所有的数据的盒子所占的大小,但是每次只控制小部分数据在上面的盒子进行显示

虚拟列表的实现:

怎么实现上面的效果?这里用到了固定定位和绝对定位

typescript 复制代码
<script lang='ts' setup>
  type Item = {
    id: number
    name: string
  }
  const allListData = ref<Item[]>([])	// 存放十万条数据
  const itemHeight = ref(40) // 每一条(项)的高度
  const count = ref(10) // 一屏展示几条数据
  const startIndex = ref(0) // 开始位置的索引
  const endIndex = ref(10) // 结束位置的索引
  const topVal = ref(0) // 父元素滚动位置

  // 计算展示的列表
  const showListData = computed(() => allListData.value.slice(startIndex.value, endIndex.value))

  // 模拟十万条数据
  const getData = async () => {
    for (let i = 0; i < 10000; i++) {
      allListData.value.push({ name: `第${i}条数据`, id: i })
    }
  }

  // 初始化加载
  onMounted(() => {
    getData()
  })

  // 虚拟列表视口区域的组件实例
  const viewport = ref<HTMLDivElement>()

    const handleScroll = () => {
      console.log('滚动了')
      // 非空判断
      if (!viewport.value) return
      // 获取滚动距离(这里通过组件实例获取的,当然也可以通过在该事件的事件参数中拿到)
      const scrollTop = viewport.value.scrollTop
      // 计算起始下标和结束下标,用于 computed 计算
      startIndex.value = Math.floor(scrollTop / itemHeight.value)
      endIndex.value = startIndex.value + count.value
      // 动态更改定位的 top 值,动态展示相应内容
      topVal.value = viewport.value.scrollTop
    }
</script>

<template>
  <!-- 虚拟列表容器 -->
  <div
    class="viewport"
    ref="viewport"
    :style="{ height: 10条数据撑开的高度 }"
    >
    <!-- 占位元素,高度为所有的数据的总高度 -->
    <div
      class="placeholder"
      :style="{ height:itemHeight * count + 'px' }"
      ></div>
    <!-- 视图区,展示10条数据,注意其定位的top值是变化的 -->
    <div class="list" :style="{ top: topVal + 'px' }">
      <!-- 每一条(项)数据 -->
      <div
        v-for="item in showListData"
        :key="item.id"
        class="item"
        :style="{ height: itemHeight + 'px' }"
        >
        {{ item.name }}
      </div>
    </div>
  </div>
</template>

  <style scoped lang="scss">
  .viewport {
  box-sizing: border-box;
  width: 240px;
  border: solid 1px #000000;
  // 开启滚动条
  overflow-y: auto;
  // 开启相对定位
  position: relative;
  .list {
  width: 100%;
  height: auto;
  // 搭配使用绝对定位
  position: absolute;
  top: 0;
  left: 0;
  .item {
  box-sizing: border-box;
  width: 100%;
  height: 40px;
  line-height: 40px;
  text-align: center;
  // 隔行变色
  &:nth-child(even) {
  background: skyblue;
  }
  &:nth-child(odd) {
  background: #fff;
  }
  }
  }
  }
</style>
  • 一开始视图区域的top值为0,刚好在最顶端,监听滚动事件,当滚动时实时改变top值以及该区域内渲染的数据,从而实现了虚拟列表
  • 视图区域的top值为什么要动态监听?试想一下,现在所有数据的总高度为1000px,视图区域为100px,当用户滚动到500px时,如果视图区域的top值不动态绑定,那么视图区域还停留在top=0(也就是最顶端处),那肯定就看不到最新的视图了
相关推荐
万少4 小时前
万少用9个AI工具,帮朋友完成了一个"不可能"的项目
前端
小小小小宇4 小时前
Vue `import` 为什么可以异步加载
前端
WMYeah4 小时前
【无标题】
前端·rust·抽奖程序·跨平台抽奖程序
Unbelievabletobe4 小时前
免费外汇api的响应时间在不同时段下的波动分析
大数据·开发语言·前端·python
大哥,带带弟弟4 小时前
Grafana 前端嵌入与 JWT 鉴权实战
前端·grafana
小小小小宇4 小时前
前端 V8 引擎垃圾回收机制与内存问题排查
前端
前端老石人4 小时前
CSS 值定义语法
前端·css
sheeta19985 小时前
Vue 前端基础笔记
前端·vue.js·笔记
小小小小宇5 小时前
GitLab + GitLab Runner + Qiankun 微前端 + Nginx + Node 中间件 前端开发机从零搭建 CI/CD 全流程
前端
前端那点事5 小时前
别再写垃圾组件!Vue3 如何设计「真正可复用」的高质量通用组件
前端·vue.js