vue长列表优化

手写vue长列表组件

手把手带你渲染10000不卡顿!

我们常常在移动端或者后台管理系统中会遇到一种恶心的需求,就是在浏览器中渲染很长很长的列表,这时候我们就需要对渲染进行优化,以优化用户体验。

  • 本文将实现浏览器渲染10000个元素 从 1.5s 到 0.3s
  • 本文也将带你自己手写一个RecycleScroller组件
  • 本文基于 vue-virtual-scroller 库进行优化 采用vue3.0语法
  "dependencies": {
    "vue": "^3.4.35",
    "vue-virtual-scroller": "^2.0.0-beta.8"
  }

一:手写RecycleScroller组件

我们采用切割,每次截取一定量的元素去渲染,通过改变偏移量来渲染元素

用法指南:

App.vue

<template>
  <div id="app">
    <RecycleScroller :items="items" :itemSize="54" class="scroller" v-slot="{ item }">
      <ListItem :item="item" />
    </RecycleScroller>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import ListItem from './components/ListItem.vue';
// import { RecycleScroller } from 'vue-virtual-scroller';//第三方库
// import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';//第三方库样式
import RecycleScroller from './components/RecycleScroller.vue' //自己写的组件

const items = ref([]);

for (let i = 0; i < 10000; i++) {
  items.value.push({
    id: i + 1,
    count: i + 1,
  });
}

</script>

<style>
#app {
  width: 100%;
  margin: 0 auto;
}

.scroller {
  width: 500px;
  margin: 0 auto;
  height: 500px;
}
</style>

list.vue

<template>
  <div class="list-item">
    <span>id{{ item.id }}</span>
    <span>name{{ item.count }}</span>
    <span>age{{ item.count }}</span>
  </div>
</template>

<script setup>
const props = defineProps({
  item: Object,
});
</script>

<style scoped>
.list-item {
  text-align: center;
  height: 54px;
  padding: 1em;
  box-sizing: border-box;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
}
</style>

RecycleScroller.vue

<template>
  <!-- 滚动容器,监听 scroll 事件,在滚动时调用 setPool 方法 -->
  <div class="recycle-scroller-container" @scroll="setPool" ref="container">
    <!-- 滚动内容的外部包装,用于设置总高度,确保滚动条的正确显示 -->
    <div class="recycle-scroller-wrapper" :style="{ height: `${totalSize}px` }">
      <!-- 使用 v-for 指令循环渲染池中的项目 -->
      <div class="recycle-scroller-item" v-for="poolItem in pool" :key="poolItem.item[keyField]" :style="{
        transform: `translateY(${poolItem.position}px)`,
      }">
        <!-- 插槽,用于插入自定义内容,传递当前项的数据给插槽 -->
        <slot :item="poolItem.item"></slot>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, watchEffect, onMounted } from 'vue';

// 定义组件的 props
const props = defineProps({
  items: {
    type: Array, // 项目列表数据的数组
    default: () => [],
  },
  itemSize: {
    type: Number, // 每个项目的固定高度
    default: 0,
  },
  keyField: {
    type: String, // 项目中用于唯一标识的字段名
    default: 'id',
  },
});

// 设置缓冲区的上下限,用于优化滚动时的渲染
const prev = 10; // 在当前视图外面上方多渲染的项目数量
const next = 10; // 在当前视图外面下方多渲染的项目数量
const pool = ref([]); // 当前渲染在视图中的项目池
const container = ref(null); // 滚动容器的 DOM 引用

// 计算属性,用于计算滚动内容的总高度
const totalSize = computed(() => props.items.length * props.itemSize);

// 更新项目池,确保只渲染在当前视图内及其缓冲区内的项目
const setPool = () => {
  if (!container.value) return; // 添加 null 检查
  const scrollTop = container.value.scrollTop; // 获取当前滚动位置
  const height = container.value.clientHeight; // 获取容器的可视高度
  let startIndex = Math.floor(scrollTop / props.itemSize); // 计算视图内第一个项目的索引
  let endIndex = Math.ceil((scrollTop + height) / props.itemSize); // 计算视图内最后一个项目的索引

  // 向前和向后扩展缓冲区
  startIndex -= prev;
  if (startIndex < 0) startIndex = 0; // 防止起始索引小于 0
  endIndex += next;

  // 计算起始位置的像素值
  const startPos = startIndex * props.itemSize;

  // 将项目池更新为新的渲染项目
  pool.value = props.items.slice(startIndex, endIndex).map((it, i) => ({
    item: it,
    position: startPos + i * props.itemSize, // 设置每个项目在页面上的垂直位置
  }));
};

// 在组件挂载时初始化项目池
onMounted(() => {
  setPool();
});

// 监听依赖变化并自动更新项目池
watchEffect(() => {
  setPool();
});
</script>

<style>
/* 滚动容器的样式,允许内容滚动 */
.recycle-scroller-container {
  overflow: auto;
}

/* 滚动内容的外部包装,设置为相对定位 */
.recycle-scroller-wrapper {
  position: relative;
}

/* 每个项目的样式,设置为绝对定位 */
.recycle-scroller-item {
  position: absolute;
  width: 100%;
  left: 0;
  top: 0;
}
</style>

二:第三方库

首先安装依赖

第二步使用:

<template>
  <div id="app">
    <RecycleScroller :items="items" :itemSize="54" class="scroller" v-slot="{ item }">
      <ListItem :item="item" />
    </RecycleScroller>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import ListItem from './components/ListItem.vue';
import { RecycleScroller } from 'vue-virtual-scroller';//第三方库
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';//第三方库样式

const items = ref([]);

for (let i = 0; i < 10000; i++) {
  items.value.push({
    id: i + 1,
    count: i + 1,
  });
}

</script>

<style>
#app {
  width: 100%;
  margin: 0 auto;
}

.scroller {
  width: 500px;
  margin: 0 auto;
  height: 500px;
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我叫汪枫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值