Implementando listas virtuales en Vue3

Tabla de contenido

I. Introducción

2. Implementación del código

0. Preparar

1. Calcular

2. correr

3. Código completo

4. Date cuenta del efecto


I. Introducción

        La era actual es la era de los grandes datos. A menudo hay miles de datos en una lista. Si los representamos uno por uno, tomará mucho tiempo, lo que resultará en una apertura lenta de la página web. Aunque la carga diferida reduce el primer tiempo de procesamiento y acelera la apertura de la página web, a medida que los datos subsiguientes se cargan y empalman continuamente, el tiempo de procesamiento de la lista será cada vez más largo. La lista virtual es una buena solución a este problema.

        La lista virtual solo representa la lista del área visible actual y no representa todos los datos. El siguiente es un ejemplo de un libro de cuentas hecho a sí mismo.

2. Implementación del código

0. Preparar

        Prepare tantos datos como sea posible, y es más intuitivo que comparar la velocidad de renderizado antes y después de usar la lista virtual.

//总数据 不需要响应式
const accountData = []
​
const getData = ()=>{
    //两万条数据 测试直接渲染卡顿大概1s左右
    for(let i = 0; i < 10000; i++) {
        accountData.push(
            {
                date: `2023-03-28`,
                state: 0,
                detail:`2月份工资`,
                money: 1800
            },
            {
                date: `2023-03-29`,
                state: 1,
                detail:'抽烟 喝酒 烫头',
                money: 2000
            })
    }
}

        Se deben preparar dos contenedores de lista, interno y externo. El contenedor externo (cuenta-lista-exterior) tiene una altura fija para generar barras de desplazamiento, y el contenedor interno (cuenta-lista-interior) se usa para expandir el contenedor externo para que la barra de desplazamiento permanezca igual que cuando la lista virtual no está en uso.

<template>
    <!-- 固定高度用于生成滚动条 -->
    <div class="account-list-outer" ref="outContainer">
        <!-- 用于撑开外部容器使得滚动条保持与未使用虚拟列表时一致 -->
        <div class="account-list-inner">
            <!-- 循环展示 -->
            <div class="account-box" v-for="(item,index) in viewData" :key="index">
                ...
            </div>
        </div>
    </div>
</template>
    
<script lang='ts' setup>
    //外部容器dom元素
    const outContainer = ref()
    //内部容器padding-top
    const paddingTop = ref('0px')
    //内部容器padding-bottom
    const paddingBottom = ref('0px')
    //最终展示数据
    const viewData = reactive([])
</script>
    
<style scoped lang='scss'>
.account-list-outer{
  height: calc(100vh - 58px);   //根据自身需求设定高度
  overflow-y: scroll;
  .account-list-inner{
    padding-top: v-bind('paddingTop');
    padding-bottom: v-bind('paddingBottom');
  }
}
</style>

1. Calcular

        En el proceso de preparación, se obtuvo el elemento dom del contenedor externo outContainery se definió la altura de la línea única, la altura del contenedor externo y la longitud de desplazamiento del eje de desplazamiento.

//单行高度
const itemHeight = 70
//外部容器高度
const outContainerHeight = outContainer.value.cilentHeight
//滚动轴滚动长度
const scrollTop = outContainer.value.scrollTop

        Estos tres valores se pueden usar para calcular los subíndices inicial y final de la ventana gráfica actual.

const startIndex = Math.floor(scrollTop / itemHeight)
const endIndex = startIndex + Math.ceil(outContainerHeight / itemHeight)

        Finalmente, se obtienen el relleno del contenedor externo requerido y los datos de información actualmente visibles.

paddingTop.value = (startIndex * itemHeight).toString() + 'px'
//accountData.length---总数据的长度
paddingBottom.value = ((accountData.length - endIndex) * itemHeight).toString() + 'px'
//清空viewData数据
viewData.splice(0, viewData.length)
//添加可视片段上的数据
viewData.push(...accountData.slice(startIndex, endIndex + 1))

2. correr

//需要获取dom元素 所以要在onMounted钩子中进行
onMounted(async () => {
  //获取原始数据(总数据)
  getData()
  //初始化创建虚拟列表
  createVirtualList()
  //添加事件监听
  outContainer.value.addEventListener('scroll', createVirtualList)
})

3. Código completo

<template>
    <div id="main-bg">
      <div class="title">记账本</div>
      <div class="account-list-outer" ref="outContainer">
        <div class="account-list-inner">
          <div class="account-box" v-for="(item,index) in viewData" :key="index">
            <p class="date" :style="{backgroundColor:item.state?'#f2ddde':'#dff1d8', color:item.state?'#ae8286':'#8ea189'}">{
   
   { item.date }}</p>
            <div class="info">
              <p class="detail">{
   
   { item.detail }}</p>
              <p class="state"><span v-if="item.state">支出</span><span v-else>收入</span></p>
              <p class="money">{
   
   {item.money}}元</p>
            </div>
          </div>
        </div>
      </div>
    </div>
</template>
​
<script lang='ts' setup>
import { ref, reactive, onMounted } from 'vue'
    
interface AccountDataItem{
  date: string //日期
  state: number //收支状态 0为收入 1为支出
  detail:string //详情
  money: number //花费或收入
}
//原始数据
const accountData: AccountDataItem[] = []
// 最终展示数据
const viewData: AccountDataItem[] = reactive([])
// 外部容器dom元素
const outContainer = ref()
// 内部容器padding-top
const paddingTop = ref('0px')
// 内部容器padding-bottom
const paddingBottom = ref('0px')
//单行高度 可少不可多
const itemHeight = 70
//外部容器高度
const outContainerHeight = outContainer.value.cilentHeight
//滚动轴滚动长度
const scrollTop = outContainer.value.scrollTop
​
//获取原始数据
const getData = ()=>{
    //两万条数据 测试直接渲染卡顿大概1s左右
    for(let i = 0; i < 10000; i++) {
        accountData.push(
            {
                date: `2023-03-28`,
                state: 0,
                detail:`2月份工资`,
                money: 1800
            },
            {
                date: `2023-03-29`,
                state: 1,
                detail:'抽烟 喝酒 烫头',
                money: 2000
            })
    }
}
​
//创建虚拟列表
const createVirtualList = () => {
  const startIndex = Math.floor(scrollTop / itemHeight)
  const endIndex = startIndex + Math.floor(outContainerHeight / itemHeight)
​
  paddingTop.value = (startIndex * itemHeight).toString() + 'px'
  // accountData.length---总数据的长度
  paddingBottom.value = ((accountData.length - endIndex) * itemHeight).toString() + 'px'
  // 清空viewData数据
  viewData.splice(0, viewData.length)
  // 添加可视片段上的数据
  viewData.push(...accountData.slice(startIndex, endIndex + 1))
}
​
// 需要获取dom元素 所以要在onMounted钩子中进行
onMounted(async () => {
  // 获取原始数据(总数据)
  await getData()
  // 初始化创建虚拟列表
  createVirtualList()
  // 添加事件监听
  outContainer.value.addEventListener('scroll', createVirtualList)
})
</script>
​
<style scoped lang='scss'>
p{
  margin: 0;
  padding: 0;
}
#main-bg{
  width: 60%;
  margin: 0 auto;
  padding: 20px 10px 0 10px;
}
.title{
  width: 100%;
  text-align: left;
  font-size: 24px;
  font-family: cursive;
  font-weight: 800;
  padding-bottom: 10px;
  border-bottom: 1px #EEEEEE solid;
}
.account-list-outer{
  height: calc(100vh - 58px);
  overflow-y: scroll;
  .account-list-inner{
    padding-top: v-bind('paddingTop');
    padding-bottom: v-bind('paddingBottom');
    .account-box{
        margin: 10px 0;
        border: 1px #EEEEEE solid;
        border-radius: 8px;
        .date{
          font-size: 14px;
          line-height: 14px;
          text-align: left;
          padding: 10px;
        }
        .info{
          font-size: 15px;
          line-height: 15px;
          text-align: left;
          padding: 10px 15px;
          display: flex;
          .detail{
            width: 60%;
          }
          .state{
            width: 10%;
          }
          .monry{
            width: 30%;
          }
    }
  }
}
}
</style>
​

4. Date cuenta del efecto

Supongo que te gusta

Origin blog.csdn.net/qq_52013792/article/details/129843850
Recomendado
Clasificación