最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Vue中實現(xiàn)虛擬列表的4種技術(shù)方案調(diào)研

 更新時間:2025年11月28日 08:51:44   作者:至簡簡  
虛擬列表Virtual List是一種優(yōu)化長列表渲染性能的技術(shù),本篇文章會從Vue3的技術(shù)框架來講解虛擬列表不同場景中的用法,感興趣的小伙伴可以了解下

背景

什么是虛擬列表

虛擬列表(Virtual List)是一種優(yōu)化長列表渲染性能的技術(shù)。其核心思想是只渲染可見區(qū)域的列表項,而不是渲染所有數(shù)據(jù)。當用戶滾動時,動態(tài)地創(chuàng)建和銷毀 DOM 元素,從而大幅減少 DOM 節(jié)點數(shù)量,提升渲染性能。

本篇文章中,筆者從Vue3的技術(shù)框架來講解虛擬列表不同場景中的用法,不涉及虛擬列表的原理。社區(qū)里有很多講解虛擬列表的原理的文章,筆者不再贅述。筆者從虛擬列表的分類上統(tǒng)籌每一種虛擬列表技術(shù)方案,并給出參考示例。

為什么使用虛擬列表

傳統(tǒng)列表的性能瓶頸:

// 渲染 10000 條數(shù)據(jù)
const items = Array.from({ length: 10000 }, (_, i) => i)

// ? 傳統(tǒng)方式:創(chuàng)建 10000 個 DOM 節(jié)點
<div v-for="item in items" :key="item">{{ item }}</div>
// 結(jié)果:首次渲染緩慢、滾動卡頓、內(nèi)存占用高

虛擬列表的優(yōu)勢:

  • 虛擬列表:只渲染可見的 ~10-20 個節(jié)點
  • 即使有 10000 條數(shù)據(jù),DOM 中只有可見區(qū)域的節(jié)點
  • 結(jié)果:快速渲染、流暢滾動、低內(nèi)存占用

@vueuse/core 簡介

@vueuse/core 是一個強大的 Vue 3 組合式 API 工具集,其中 useVirtualList 提供了開箱即用的虛擬列表解決方案。后續(xù)所有的示例代碼均使用 useVirtualList 實現(xiàn)

快速開始

npm install @vueuse/core
# 或
pnpm add @vueuse/core

不得不說,Vue的技術(shù)生態(tài)中,很多第三方依賴開箱即用,這一點非常棒!

useVirtualList API 詳解

基本用法

import { useVirtualList } from '@vueuse/core'

const { list, containerProps, wrapperProps } = useVirtualList(
  items,    // 數(shù)據(jù)源
  options   // 配置選項
)

參數(shù)說明

items (必需)

類型: MaybeRef<T[]>

說明: 要渲染的數(shù)據(jù)數(shù)組,可以是響應式引用或普通數(shù)組

// 方式 1: 響應式引用
const items = ref([1, 2, 3, ...])

// 方式 2: 普通數(shù)組
const items = [1, 2, 3, ...]

// 方式 3: computed
const items = computed(() => originalData.filter(...))

options (必需)

類型: UseVirtualListOptions

選項類型默認值說明
itemHeightnumber | (index: number) => number必需項目高度,可以是固定值或計算函數(shù)
overscannumber5預渲染的額外項目數(shù)量,提升滾動流暢度

返回值說明

list

類型: ComputedRef<ListItem<T>[]>

說明: 當前應該渲染的列表項數(shù)組

interface ListItem<T> {
  index: number    // 在原數(shù)組中的索引
  data: T         // 原始數(shù)據(jù)
}

使用示例:

<div v-for="item in list" :key="item.index">
  <div>索引: {{ item.index }}</div>
  <div>數(shù)據(jù): {{ item.data }}</div>
</div>

containerProps (非常重要)

類型: Object

說明: 需要綁定到容器元素的屬性對象

包含屬性:

  • ref: 容器的引用
  • onScroll: 滾動事件處理函數(shù)
  • style: 容器樣式(可能包含 overflow 等)

使用方式:

<div v-bind="containerProps" style="height: 400px; overflow-y: auto;">
  <!-- 內(nèi)容 -->
</div>

wrapperProps (非常重要)

類型: Object

說明: 需要綁定到包裝器元素的屬性對象

包含屬性:

style: 包裝器樣式,包含計算的總高度

作用:

  • 設(shè)置內(nèi)部容器的總高度(基于所有項目的總高度)
  • 創(chuàng)建正確的滾動區(qū)域
  • 支持虛擬化定位

使用方式:

<div v-bind="containerProps">
  <div v-bind="wrapperProps">
    <!-- 列表項 -->
  </div>
</div>

工作原理

關(guān)鍵點:

  • 容器(Container): 固定高度,overflow 可滾動
  • 包裝器(Wrapper): 高度 = 所有項目的總高度
  • 列表項: 只渲染可見區(qū)域的項目 (useVirtualList返回的 listitems)

四種虛擬列表實現(xiàn)方案

經(jīng)過筆者的調(diào)研,主要有四種虛擬列表技術(shù)方案

固定項高度 + 固定容器高度

方案特點

  • 項目高度: 固定(如 50px)
  • 容器高度: 固定(如 400px)
  • 適用場景: 標準列表、表格、聊天記錄等統(tǒng)一高度的場景

適用場景

場景說明
數(shù)據(jù)列表表格數(shù)據(jù)、商品列表、用戶列表
日志查看器系統(tǒng)日志、操作記錄
聊天記錄簡單文本消息列表
文件瀏覽器文件/文件夾列表

完整實現(xiàn)代碼

組件代碼: FixedHeightVirtualList.vue

<script setup>
import { ref } from "vue";
import { useVirtualList } from "@vueuse/core";

const props = defineProps({
  items: {
    type: Array,
    default: () => [],
    required: true,
  },
  itemHeight: {
    type: Number,
    default: 50,
  },
  containerHeight: {
    type: Number,
    default: 400,
  },
});

const container = ref(null);

// ?? 關(guān)鍵配置:itemHeight 為固定數(shù)值
const { list, containerProps, wrapperProps } = useVirtualList(props.items, {
  itemHeight: props.itemHeight,  // 固定高度
  overscan: 5,
});
</script>

<template>
  <div
    ref="container"
    class="virtual-list-container"
    :style="{ height: `${containerHeight}px` }"  <!-- 固定容器高度 -->
    v-bind="containerProps"
  >
    <div class="virtual-list-wrapper" v-bind="wrapperProps">
      <div
        v-for="item in list"
        :key="item.index"
        class="virtual-list-item"
        :style="{ height: `${itemHeight}px` }"  <!-- 固定項目高度 -->
      >
        <div class="item-content">
          <span class="item-index">{{ item.data.id }}</span>
          <span class="item-text">{{ item.data.name }}</span>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
.virtual-list-container {
  overflow: hidden;  /* ?? 關(guān)鍵:overflow hidden */
  border: 1px solid #e8e8e8;
  border-radius: 8px;
}

.virtual-list-item {
  display: flex;
  align-items: center;
  border-bottom: 1px solid #f0f0f0;
}
</style>

使用示例

<script setup>
import { ref } from 'vue'
import FixedHeightVirtualList from './components/FixedHeightVirtualList.vue'

const items = ref(
  Array.from({ length: 1000 }, (_, index) => ({
    id: index + 1,
    name: `項目 ${index + 1}`
  }))
)
</script>

<template>
  <FixedHeightVirtualList
    :items="items"
    :item-height="50"
    :container-height="400"
  />
</template>

優(yōu)點

  • 實現(xiàn)簡單,性能最優(yōu)
  • 滾動最流暢
  • 計算開銷最小

缺點

  • 所有項目必須高度一致
  • 容器高度固定,不夠靈活

固定項高度 + 動態(tài)容器高度

方案特點

  • 項目高度: 固定(如 50px)
  • 容器高度: 動態(tài)自適應(max-height)
  • 適用場景: 響應式布局、下拉菜單、彈窗列表

適用場景

場景說明
下拉選擇器數(shù)據(jù)量不確定的下拉列表
搜索建議搜索結(jié)果列表(數(shù)量動態(tài)變化)
彈窗列表Modal 中的列表內(nèi)容
側(cè)邊欄菜單響應式側(cè)邊導航
響應式布局需要適應不同屏幕尺寸

完整實現(xiàn)代碼

組件代碼: DynamicHeightVirtualList.vue

<script setup>
import { ref } from "vue";
import { useVirtualList } from "@vueuse/core";

const props = defineProps({
  items: {
    type: Array,
    default: () => [],
    required: true,
  },
  itemHeight: {
    type: Number,
    default: 50,
  },
  maxHeight: {
    type: Number,
    default: 500,
  },
});

const container = ref(null);

// ?? 關(guān)鍵配置:itemHeight 為固定數(shù)值
const { list, containerProps, wrapperProps } = useVirtualList(props.items, {
  itemHeight: props.itemHeight,
  overscan: 5,
});
</script>

<template>
  <div
    ref="container"
    class="virtual-list-container"
    :style="{ maxHeight: `${maxHeight}px` }"  <!-- ?? max-height 實現(xiàn)動態(tài) -->
    v-bind="containerProps"
  >
    <div class="virtual-list-wrapper" v-bind="wrapperProps">
      <div
        v-for="item in list"
        :key="item.index"
        class="virtual-list-item"
        :style="{ height: `${itemHeight}px` }"
      >
        <div class="item-content">
          <span class="item-index">{{ item.data.id }}</span>
          <span class="item-text">{{ item.data.name }}</span>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
.virtual-list-container {
  overflow-y: auto;  /* ?? 關(guān)鍵:overflow-y auto */
  border: 1px solid #e8e8e8;
  border-radius: 8px;
}
</style>

使用示例

<script setup>
import { ref } from 'vue'
import DynamicHeightVirtualList from './components/DynamicHeightVirtualList.vue'

// 數(shù)據(jù)量少
const fewItems = ref(
  Array.from({ length: 4 }, (_, index) => ({
    id: index + 1,
    name: `項目 ${index + 1}`
  }))
)

// 數(shù)據(jù)量多
const manyItems = ref(
  Array.from({ length: 1000 }, (_, index) => ({
    id: index + 1,
    name: `項目 ${index + 1}`
  }))
)
</script>

<template>
  <!-- 數(shù)據(jù)少時,容器高度 = 4 * 50 = 200px -->
  <DynamicHeightVirtualList
    :items="fewItems"
    :item-height="50"
    :max-height="400"
  />

  <!-- 數(shù)據(jù)多時,容器高度 = max-height = 400px,出現(xiàn)滾動條 -->
  <DynamicHeightVirtualList
    :items="manyItems"
    :item-height="50"
    :max-height="400"
  />
</template>

關(guān)鍵差異對比

屬性固定容器動態(tài)容器
容器樣式height: 400pxmax-height: 400px
overflowoverflow: hiddenoverflow-y: auto
數(shù)據(jù)少時仍占據(jù) 400px自動縮小
數(shù)據(jù)多時顯示滾動條顯示滾動條

優(yōu)點

  • 容器高度自適應,更靈活
  • 數(shù)據(jù)少時不浪費空間
  • 性能依然優(yōu)秀

缺點

  • 需要正確處理 overflow-y
  • 布局可能因高度變化而跳動

動態(tài)項高度 + 固定容器高度

方案特點

  • 項目高度: 動態(tài)計算(通過函數(shù))
  • 容器高度: 固定(如 450px)
  • 適用場景: 多樣化內(nèi)容、卡片列表、復雜布局

適用場景

場景說明
新聞列表標題長度不一,內(nèi)容預覽不同
卡片流不同類型的卡片高度不同
評論列表評論內(nèi)容長度不一
商品展示不同商品信息復雜度不同
郵件列表帶附件、標簽等額外信息

完整實現(xiàn)代碼

組件代碼: VariableHeightVirtualList.vue

<script setup>
import { ref } from "vue";
import { useVirtualList } from "@vueuse/core";

const props = defineProps({
  items: {
    type: Array,
    default: () => [],
    required: true,
  },
  containerHeight: {
    type: Number,
    default: 400,
  },
});

const container = ref(null);

// ?? 關(guān)鍵:動態(tài)計算每個項的高度
const getItemHeight = (index) => {
  // 方式 1: 根據(jù)索引模式
  const heightPattern = index % 3;
  if (heightPattern === 0) return 60;  // 小項
  if (heightPattern === 1) return 80;  // 中項
  return 100;                          // 大項
  
  // 方式 2: 根據(jù)數(shù)據(jù)內(nèi)容
  // const item = props.items[index];
  // return item.type === 'large' ? 100 : 50;
  
  // 方式 3: 根據(jù)文本長度
  // const textLength = props.items[index].text.length;
  // return Math.max(50, Math.ceil(textLength / 20) * 30);
};

// ?? 關(guān)鍵配置:itemHeight 為函數(shù)
const { list, containerProps, wrapperProps } = useVirtualList(props.items, {
  itemHeight: getItemHeight,  // 函數(shù),不是數(shù)值!
  overscan: 5,
});
</script>

<template>
  <div
    ref="container"
    class="virtual-list-container"
    :style="{ height: `${containerHeight}px` }"
    v-bind="containerProps"
  >
    <div class="virtual-list-wrapper" v-bind="wrapperProps">
      <div
        v-for="item in list"
        :key="item.index"
        class="virtual-list-item"
        :style="{ height: `${getItemHeight(item.index)}px` }"  <!-- 動態(tài)高度 -->
        :data-height="getItemHeight(item.index)"
      >
        <div class="item-content">
          <span class="item-index">{{ item.data.id }}</span>
          <div class="item-info">
            <span class="item-text">{{ item.data.name }}</span>
            <span class="item-height-tag">高度: {{ getItemHeight(item.index) }}px</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
.virtual-list-container {
  overflow: hidden;
}

/* 根據(jù)高度添加不同的視覺樣式 */
.virtual-list-item[data-height="60"] {
  background: linear-gradient(135deg, #fff9e6 0%, #ffffff 100%);
}

.virtual-list-item[data-height="80"] {
  background: linear-gradient(135deg, #e6f7ff 0%, #ffffff 100%);
}

.virtual-list-item[data-height="100"] {
  background: linear-gradient(135deg, #f0f9ff 0%, #ffffff 100%);
}
</style>

使用示例

<script setup>
import { ref } from 'vue'
import VariableHeightVirtualList from './components/VariableHeightVirtualList.vue'

const items = ref(
  Array.from({ length: 400 }, (_, index) => ({
    id: index + 1,
    name: `項目 ${index + 1}`
  }))
)
</script>

<template>
  <VariableHeightVirtualList
    :items="items"
    :container-height="450"
  />
</template>

高度計算函數(shù)示例

// 示例 1: 基于索引的規(guī)律模式
const getItemHeight = (index) => {
  return 50 + (index % 3) * 25;  // 50, 75, 100 循環(huán)
};

// 示例 2: 基于數(shù)據(jù)類型
const getItemHeight = (index) => {
  const item = props.items[index];
  switch(item.type) {
    case 'header': return 80;
    case 'content': return 120;
    case 'footer': return 60;
    default: return 50;
  }
};

// 示例 3: 基于內(nèi)容長度
const getItemHeight = (index) => {
  const item = props.items[index];
  const textLength = item.description?.length || 0;
  const lines = Math.ceil(textLength / 40);  // 假設(shè)每行 40 字符
  return 50 + (lines - 1) * 20;  // 基礎(chǔ)高度 + 額外行高度
};

// 示例 4: 基于屬性組合
const getItemHeight = (index) => {
  const item = props.items[index];
  let height = 60;  // 基礎(chǔ)高度
  if (item.hasImage) height += 200;
  if (item.hasTags) height += 30;
  if (item.hasActions) height += 40;
  return height;
};

重要注意事項

  • 高度必須可預測: getItemHeight(index) 對同一個 index 必須始終返回相同的值
  • 性能考慮: 函數(shù)會被頻繁調(diào)用,避免復雜計算
  • 實際高度匹配: CSS 實際高度必須與計算高度一致
// ? 錯誤:隨機高度(不可預測)
const getItemHeight = (index) => {
  return 50 + Math.random() * 50;  // 每次調(diào)用結(jié)果不同!
};

// ? 錯誤:異步計算
const getItemHeight = async (index) => {
  const data = await fetchData(index);  // 不支持異步!
  return data.height;
};

// ? 正確:可預測、同步
const getItemHeight = (index) => {
  return props.items[index].preCalculatedHeight;  // 提前計算好的高度
};

優(yōu)點

  • 支持不同高度的項目
  • 容器高度固定,布局穩(wěn)定
  • 視覺效果更豐富

缺點

  • 高度必須提前可計算
  • 不支持真正的動態(tài)內(nèi)容(如圖片加載后才知道高度)
  • 計算開銷略高于固定高度

動態(tài)項高度 + 動態(tài)容器高度

方案特點

  • 項目高度: 動態(tài)計算(通過函數(shù))
  • 容器高度: 動態(tài)自適應(max-height)
  • 適用場景: 最靈活的場景,結(jié)合前三種優(yōu)勢

適用場景

場景說明
復雜彈窗彈窗內(nèi)的復雜列表,高度不確定
搜索結(jié)果不同類型的搜索結(jié)果混合
通知中心不同類型通知,數(shù)量動態(tài)變化
購物車不同商品信息,數(shù)量動態(tài)
活動頁面不同類型的活動卡片

完整實現(xiàn)代碼

組件代碼: FullyDynamicVirtualList.vue

<script setup>
import { ref } from "vue";
import { useVirtualList } from "@vueuse/core";

const props = defineProps({
  items: {
    type: Array,
    default: () => [],
    required: true,
  },
  maxHeight: {
    type: Number,
    default: 500,
  },
});

const container = ref(null);

// ?? 關(guān)鍵:動態(tài)計算每個項的高度
const getItemHeight = (index) => {
  const heightPattern = index % 3;
  if (heightPattern === 0) return 60;
  if (heightPattern === 1) return 80;
  return 100;
};

// ?? 關(guān)鍵配置:itemHeight 為函數(shù) + 容器使用 max-height
const { list, containerProps, wrapperProps } = useVirtualList(props.items, {
  itemHeight: getItemHeight,
  overscan: 5,
});
</script>

<template>
  <div
    ref="container"
    class="virtual-list-container"
    :style="{ maxHeight: `${maxHeight}px` }"  <!-- ?? max-height 動態(tài)容器 -->
    v-bind="containerProps"
  >
    <div class="virtual-list-wrapper" v-bind="wrapperProps">
      <div
        v-for="item in list"
        :key="item.index"
        class="virtual-list-item"
        :style="{ height: `${getItemHeight(item.index)}px` }"
        :data-height="getItemHeight(item.index)"
      >
        <div class="item-content">
          <span class="item-index">{{ item.data.id }}</span>
          <div class="item-info">
            <span class="item-text">{{ item.data.name }}</span>
            <span class="item-height-tag">高度: {{ getItemHeight(item.index) }}px</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
.virtual-list-container {
  overflow-y: auto;  /* ?? 關(guān)鍵:overflow-y auto */
  border: 1px solid #e8e8e8;
  border-radius: 8px;
}
</style>

使用示例

<script setup>
import { ref } from 'vue'
import FullyDynamicVirtualList from './components/FullyDynamicVirtualList.vue'

const items = ref(
  Array.from({ length: 200 }, (_, index) => ({
    id: index + 1,
    name: `項目 ${index + 1}`
  }))
)
</script>

<template>
  <FullyDynamicVirtualList
    :items="items"
    :max-height="600"
  />
</template>

適配不同數(shù)據(jù)量

<template>
  <!-- 數(shù)據(jù)少:容器自動縮小 -->
  <FullyDynamicVirtualList
    :items="[1, 2, 3]"
    :max-height="400"
  />
  <!-- 實際高度約: 60 + 80 + 100 = 240px -->

  <!-- 數(shù)據(jù)多:容器達到最大高度,顯示滾動條 -->
  <FullyDynamicVirtualList
    :items="Array(200)"
    :max-height="400"
  />
  <!-- 實際高度: 400px (max-height) -->
</template>

優(yōu)點

  • 最大的靈活性
  • 項目高度可變
  • 容器高度自適應
  • 適應各種復雜場景

缺點

  • 實現(xiàn)相對復雜
  • 需要同時處理兩種動態(tài)性
  • 性能略低于前三種方案

性能對比與最佳實踐

性能對比

方案性能靈活性實現(xiàn)難度適用場景
固定+固定????????標準列表
固定+動態(tài)??????????響應式布局
動態(tài)+固定???????????多樣化內(nèi)容
動態(tài)+動態(tài)?????????????復雜場景

渲染效率對比

測試條件: 10,000 條數(shù)據(jù)

方案DOM 節(jié)點數(shù)首次渲染時間滾動 FPS內(nèi)存占用
傳統(tǒng)列表10,000~2000ms<30 FPS~50MB
虛擬列表(固定+固定)~15~50ms60 FPS~5MB
虛擬列表(固定+動態(tài))~15~50ms60 FPS~5MB
虛擬列表(動態(tài)+固定)~15~60ms55-60 FPS~5MB
虛擬列表(動態(tài)+動態(tài))~15~60ms55-60 FPS~5MB

方案選擇決策樹

開始
  │
  ├─ 所有項目高度是否相同?
  │   ├─ 是 ──> 容器高度是否固定?
  │   │         ├─ 是 ──> 【方案1: 固定+固定】最優(yōu)性能 ?????
  │   │         └─ 否 ──> 【方案2: 固定+動態(tài)】響應式布局 ?????
  │   │
  │   └─ 否 ──> 容器高度是否固定?
  │             ├─ 是 ──> 【方案3: 動態(tài)+固定】多樣化內(nèi)容 ????
  │             └─ 否 ──> 【方案4: 動態(tài)+動態(tài)】最大靈活性 ????

最佳實踐

overscan 參數(shù)調(diào)優(yōu)

// 基礎(chǔ)配置
overscan: 5  // 默認值,適合大多數(shù)場景

// 快速滾動場景
overscan: 10  // 增加預渲染,防止白屏

// 性能敏感場景
overscan: 3  // 減少預渲染,降低開銷

// 移動端
overscan: 8  // 移動端滾動更快,建議增加

高度計算緩存

// ? 不推薦:每次都計算
const getItemHeight = (index) => {
  const item = props.items[index];
  return calculateComplexHeight(item);  // 復雜計算
};

// ? 推薦:緩存計算結(jié)果
const heightCache = new Map();

const getItemHeight = (index) => {
  if (heightCache.has(index)) {
    return heightCache.get(index);
  }
  
  const item = props.items[index];
  const height = calculateComplexHeight(item);
  heightCache.set(index, height);
  return height;
};

// 數(shù)據(jù)變化時清除緩存
watch(() => props.items, () => {
  heightCache.clear();
}, { deep: true });

響應式優(yōu)化

// ? 不推薦:直接傳入響應式對象
const items = reactive([...]);

// ? 推薦:使用 ref
const items = ref([...]);

// ? 更好:使用 shallowRef(大數(shù)據(jù)量)
const items = shallowRef([...]);

大數(shù)據(jù)量優(yōu)化

// 數(shù)據(jù)量 > 50,000 時的優(yōu)化策略

// 1. 使用 shallowRef
const items = shallowRef(largeDataArray);

// 2. 增加 overscan
overscan: 15

// 3. 防抖滾動事件(如果需要自定義處理)
const handleScroll = useDebounceFn(() => {
  // 處理滾動
}, 16);  // 約 60fps

// 4. 虛擬滾動條(可選)
// 使用自定義滾動條替代原生滾動條

避免常見錯誤

// ? 錯誤 1:在模板中直接訪問原數(shù)組
<template>
  <div v-for="item in items">  <!-- 錯誤!應該用 list -->
    {{ item }}
  </div>
</template>

// ? 正確:使用 list
<template>
  <div v-for="item in list">  <!-- 正確!-->
    {{ item.data }}
  </div>
</template>

// ? 錯誤 2:忘記綁定 props
<div class="container">  <!-- 錯誤!缺少 v-bind -->
  <div class="wrapper">  <!-- 錯誤!缺少 v-bind -->

// ? 正確:綁定 containerProps 和 wrapperProps
<div v-bind="containerProps">
  <div v-bind="wrapperProps">

// ? 錯誤 3:高度計算不準確
.item {
  height: 50px;
  padding: 10px;  /* 實際高度 = 50 + 20 = 70px */
}

// ? 正確:確保計算高度包含 padding/border
itemHeight: 70  // 或使用 box-sizing: border-box

常見問題與解決方案

滾動位置跳動

問題: 滾動時列表位置跳動

原因: 計算高度與實際高度不匹配

解決方案:

// 1. 使用 box-sizing: border-box
.virtual-list-item {
  box-sizing: border-box;
  height: 50px;  /* 包含 padding 和 border */
  padding: 10px;
}

// 2. 確保高度計算準確
const getItemHeight = (index) => {
  const baseHeight = 50;
  const padding = 20;  // top + bottom
  const border = 2;    // top + bottom
  return baseHeight + padding + border;
};

首次渲染白屏

問題: 首次加載時短暫白屏

原因: overscan 太小或數(shù)據(jù)加載慢

解決方案:

// 1. 增加 overscan
overscan: 10

// 2. 添加骨架屏
<template>
  <div v-if="loading">
    <SkeletonItem v-for="i in 10" :key="i" />
  </div>
  <div v-else v-bind="containerProps">
    <!-- 虛擬列表 -->
  </div>
</template>

// 3. 預加載數(shù)據(jù)
onMounted(async () => {
  loading.value = true;
  items.value = await fetchData();
  loading.value = false;
});

滾動到指定位置

問題: 如何滾動到指定索引的項目

解決方案:

// 方法 1: 計算滾動位置(固定高度)
const scrollToIndex = (index) => {
  const scrollTop = index * itemHeight;
  container.value.scrollTop = scrollTop;
};

// 方法 2: 計算滾動位置(動態(tài)高度)
const scrollToIndex = (index) => {
  let scrollTop = 0;
  for (let i = 0; i < index; i++) {
    scrollTop += getItemHeight(i);
  }
  container.value.scrollTop = scrollTop;
};

// 方法 3: 使用 scrollIntoView
const scrollToIndex = (index) => {
  // 需要在 list 中找到對應的元素
  const element = document.querySelector(`[data-index="${index}"]`);
  element?.scrollIntoView({ behavior: 'smooth', block: 'start' });
};

數(shù)據(jù)更新后滾動位置重置

問題: 數(shù)據(jù)更新后滾動位置跳回頂部

原因: items 引用變化導致重新計算

解決方案:

// ? 錯誤:直接賦值新數(shù)組
items.value = newData;  // 引用變化,滾動位置重置

// ? 正確:保持引用,修改內(nèi)容
// 方法 1: 使用 splice
items.value.splice(0, items.value.length, ...newData);

// 方法 2: 逐項更新
newData.forEach((item, index) => {
  items.value[index] = item;
});

// 方法 3: 記錄并恢復滾動位置
const scrollTop = container.value.scrollTop;
items.value = newData;
nextTick(() => {
  container.value.scrollTop = scrollTop;
});

在 TypeScript 中使用

import { ref, Ref } from 'vue'
import { useVirtualList, UseVirtualListOptions } from '@vueuse/core'

interface ListItem {
  id: number
  name: string
  description?: string
}

const items: Ref<ListItem[]> = ref([
  { id: 1, name: 'Item 1' },
  { id: 2, name: 'Item 2' }
])

const options: UseVirtualListOptions = {
  itemHeight: 50,
  overscan: 5
}

const { list, containerProps, wrapperProps } = useVirtualList(items, options)

// list 的類型是 ComputedRef<{ index: number; data: ListItem }[]>

服務端渲染 (SSR) 支持

// 在 SSR 環(huán)境中,需要注意以下幾點:

// 1. 容器高度必須明確
<div :style="{ height: '400px' }">  <!-- SSR 需要明確高度 -->

// 2. 避免在服務端計算滾動
import { onMounted } from 'vue'

const setupVirtualList = () => {
  if (typeof window === 'undefined') return  // SSR 環(huán)境跳過
  
  const { list, containerProps, wrapperProps } = useVirtualList(...)
  return { list, containerProps, wrapperProps }
}

// 3. 使用 ClientOnly 組件(Nuxt.js)
<ClientOnly>
  <VirtualList :items="items" />
</ClientOnly>

總結(jié)與對比表

四種方案完整對比

特性固定+固定固定+動態(tài)動態(tài)+固定動態(tài)+動態(tài)
項目高度固定值固定值函數(shù)計算函數(shù)計算
容器高度heightmax-heightheightmax-height
overflowhiddenautohiddenauto
itemHeight5050(i) => ...(i) => ...
性能評分5/55/54/54/5
靈活性評分2/53/54/55/5
實現(xiàn)難度簡單簡單中等中等
典型應用表格下拉菜單卡片列表復雜彈窗
數(shù)據(jù)量建議無限制無限制< 100,000< 100,000

API 參數(shù)速查表

// useVirtualList 配置
const { list, containerProps, wrapperProps } = useVirtualList(items, {
  itemHeight: 50 | ((index: number) => number),  // 必需
  overscan: 5                                     // 可選,默認 5
})

關(guān)鍵代碼片段速查

固定高度配置

itemHeight: 50

動態(tài)高度配置

itemHeight: (index) => {
  return index % 2 === 0 ? 60 : 80
}

固定容器

<div :style="{ height: '400px' }" v-bind="containerProps">

動態(tài)容器

<div :style="{ maxHeight: '400px' }" v-bind="containerProps">

完整模板結(jié)構(gòu)

<div v-bind="containerProps" :style="{ height: '400px' }">
  <div v-bind="wrapperProps">
    <div v-for="item in list" :key="item.index">
      {{ item.data }}
    </div>
  </div>
</div>

完整項目結(jié)構(gòu)

vue3-sample/
├── src/
│   ├── components/
│   │   ├── FixedHeightVirtualList.vue       # 固定+固定
│   │   ├── DynamicHeightVirtualList.vue     # 固定+動態(tài)
│   │   ├── VariableHeightVirtualList.vue    # 動態(tài)+固定
│   │   └── FullyDynamicVirtualList.vue      # 動態(tài)+動態(tài)
│   ├── App.vue                               # 使用示例
│   └── main.js
├── docs/
│   └── virtual-list-guide.md                 # 本文檔
├── package.json
└── README.md

快速開始

# 1. 克隆項目
git clone https://github.com/ilcherry/virtuallist-examples

# 2. 安裝依賴
cd vue3-sample
pnpm install

# 3. 運行開發(fā)服務器
pnpm dev

# 4. 訪問
# 打開瀏覽器訪問 http://localhost:5173

以上就是Vue中實現(xiàn)虛擬列表的4種技術(shù)方案調(diào)研的詳細內(nèi)容,更多關(guān)于Vue虛擬列表的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Vue3+TS實現(xiàn)語音播放組件的示例代碼

    Vue3+TS實現(xiàn)語音播放組件的示例代碼

    這篇文章主要介紹了如何利用Vue+TS實現(xiàn)一個簡易的語音播放組件,文中的示例代碼講解詳細,對我們學習Vue有一定的幫助,需要的可以參考一下
    2022-03-03
  • 分分鐘玩轉(zhuǎn)Vue.js組件(二)

    分分鐘玩轉(zhuǎn)Vue.js組件(二)

    這篇文章教大家如何分分鐘玩轉(zhuǎn)Vue.js組件,完善了vue.js組件的學習資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • 基于vue cli重構(gòu)多頁面腳手架過程詳解

    基于vue cli重構(gòu)多頁面腳手架過程詳解

    本文分步驟給大家介紹了基于vue cli重構(gòu)多頁面腳手架過程,非常不錯,具有參考借鑒價值,需要的朋友參考下
    2018-01-01
  • vue中的計算屬性傳參

    vue中的計算屬性傳參

    這篇文章主要介紹了vue中的計算屬性傳參,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • vue.js實現(xiàn)h5機器人聊天(測試版)

    vue.js實現(xiàn)h5機器人聊天(測試版)

    這篇文章主要為大家詳細介紹了vue.js實現(xiàn)h5機器人聊天測試版,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-07-07
  • Element 頭像上傳的實戰(zhàn)

    Element 頭像上傳的實戰(zhàn)

    頭像上傳在很多實例中都可以用到,本文主要介紹了Element 頭像上傳,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • vue+springboot+element+vue-resource實現(xiàn)文件上傳教程

    vue+springboot+element+vue-resource實現(xiàn)文件上傳教程

    這篇文章主要介紹了vue+springboot+element+vue-resource實現(xiàn)文件上傳教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-10-10
  • Vue3和Electron實現(xiàn)桌面端應用詳解

    Vue3和Electron實現(xiàn)桌面端應用詳解

    本文主要介紹了Vue3和Electron實現(xiàn)桌面端應用詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-07-07
  • vue init webpack myproject構(gòu)建項目 ip不能訪問的解決方法

    vue init webpack myproject構(gòu)建項目 ip不能訪問的解決方法

    下面小編就為大家分享一篇vue init webpack myproject構(gòu)建項目 ip不能訪問的解決方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-03-03
  • vue-drawer-layout實現(xiàn)手勢滑出菜單欄

    vue-drawer-layout實現(xiàn)手勢滑出菜單欄

    這篇文章主要為大家詳細介紹了vue-drawer-layout實現(xiàn)手勢滑出菜單欄,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-11-11

最新評論

芜湖市| 渝北区| 龙川县| 马山县| 绵竹市| 武强县| 汨罗市| 沧州市| 额尔古纳市| 湘西| 简阳市| 营口市| 南涧| 咸丰县| 沿河| 卢龙县| 贡山| 济宁市| 泽库县| 拜泉县| 壶关县| 堆龙德庆县| 六安市| 辽阳县| 广元市| 石城县| 永宁县| 万安县| 江城| 美姑县| 高唐县| 新沂市| 西昌市| 论坛| 宽甸| 留坝县| 伊宁市| 工布江达县| 澄江县| 怀柔区| 衡水市|