偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

5 萬(wàn)條數(shù)據(jù)不卡!虛擬列表終極方案來(lái)了!

云計(jì)算 虛擬化
大多數(shù)同學(xué)針對(duì)虛擬列表時(shí),都會(huì)說(shuō):服務(wù)端一口氣給我們返回 幾萬(wàn)條數(shù)據(jù),我們通過(guò)虛擬列表的方式進(jìn)行渲染。

說(shuō)到虛擬列表應(yīng)該沒(méi)有同學(xué)不知道吧,這是目前很多同學(xué)面試的時(shí)候經(jīng)常會(huì)作為項(xiàng)目難點(diǎn)來(lái)描述的內(nèi)容。

大多數(shù)同學(xué)針對(duì)虛擬列表時(shí),都會(huì)說(shuō):服務(wù)端一口氣給我們返回 幾萬(wàn)條數(shù)據(jù),我們通過(guò)虛擬列表的方式進(jìn)行渲染。

然后,面試官通常都會(huì)通過(guò)一句話堵死你:為啥服務(wù)端一定要返回幾萬(wàn)條數(shù)據(jù)呢?

額。。。尷尬啊。。。

所以,咱們今天這篇文章,就主要解決 面試聊到虛擬列表 的兩個(gè)核心問(wèn)題:

  1. 虛擬列表的 真實(shí)體現(xiàn)場(chǎng)景 是什么?
  2. 虛擬列表的 終極解決方案 是什么?

真實(shí)體現(xiàn)場(chǎng)景

面試官問(wèn)的沒(méi)錯(cuò):正常情況下,服務(wù)端沒(méi)必要一次性返回 5 萬(wàn)條數(shù)據(jù)。

那為什么我們還需要虛擬列表呢?

其實(shí),在實(shí)際業(yè)務(wù)中,虛擬列表的需求非常普遍,遠(yuǎn)遠(yuǎn)不止“服務(wù)端一次性返回幾萬(wàn)條”。

下面給大家拆幾個(gè)真實(shí)落地的場(chǎng)景:

1. 后臺(tái)系統(tǒng):訂單、日志管理

做后臺(tái)開(kāi)發(fā)的同學(xué)一定熟悉:訂單列表、操作日志、用戶流水,動(dòng)輒幾十萬(wàn)條。

雖然前端一般會(huì)分頁(yè),但有時(shí)候業(yè)務(wù)場(chǎng)景要求:

  • 支持無(wú)限滾動(dòng)加載(例如用戶下拉快速翻查訂單)。
  • 支持快速定位(跳轉(zhuǎn)到第 N 頁(yè)、第 N 條)。

這種場(chǎng)景下,即便分批請(qǐng)求,只要用戶不斷的上啦加載更多的數(shù)據(jù),前端依然要承載數(shù)萬(wàn)條數(shù)據(jù)的渲染。

2. 電商、內(nèi)容流:無(wú)限加載

在 App 或 H5 頁(yè)面,商品流、視頻流、評(píng)論區(qū)等業(yè)務(wù)場(chǎng)景普遍采用“無(wú)限滾動(dòng)”的交互。

既:用戶可以一直往下滑,直到加載幾千、甚至上萬(wàn)條數(shù)據(jù)。

那么在這種情況下,如果你直接用 v-for 渲染所有數(shù)據(jù),內(nèi)存和 DOM 數(shù)量很快就爆掉。

所以,此時(shí)就必須要使用 虛擬列表 了

3. 數(shù)據(jù)大屏:實(shí)時(shí)推送

很多公司都會(huì)做數(shù)據(jù)可視化大屏,這種項(xiàng)目又一個(gè)特點(diǎn),那就是: 實(shí)時(shí)展示告警流、消息流、交易流水,并且數(shù)據(jù)量是實(shí)時(shí)刷新的、不斷累積的

同時(shí),需求通常要求“全量展示”,不能只保留最新幾條。

這種情況下,傳統(tǒng)渲染很快就頂不住了,只有 虛擬列表才能保證大屏不卡頓

4. 聊天、IM:動(dòng)態(tài)高度 & 無(wú)限消息

聊天窗口也是典型場(chǎng)景之一。

通常情況下,聊天記錄會(huì)隨著用戶滾動(dòng)不斷加載歷史消息,每條消息高度還可能不一致(文本、圖片、語(yǔ)音混合)。

那么在這樣的條件下,我們又必須要保證加載上萬(wàn)條消息依然流暢,還要支持“滾動(dòng)到底部”邏輯。

這類場(chǎng)景更復(fù)雜,需要虛擬列表的動(dòng)態(tài)高度方案。

終極解決方案

上面聊了真實(shí)場(chǎng)景,那么問(wèn)題來(lái)了:虛擬列表到底是怎么解決幾萬(wàn)條數(shù)據(jù)不卡頓的?

一句話總結(jié):

虛擬列表的核心就是:只渲染用戶能看到的部分,其他內(nèi)容用“假的”代替。

三大核心要素

要讓虛擬列表真正跑起來(lái),必須搞定這三個(gè)關(guān)鍵點(diǎn):

  1. 可視區(qū)渲染:頁(yè)面上顯示多少內(nèi)容,就只渲染這些內(nèi)容。比如屏幕高度能容納 10 條數(shù)據(jù),那就只創(chuàng)建 10 條 DOM,而不是 50000 條。
  2. 緩沖區(qū):滾動(dòng)時(shí)如果只渲染剛好可見(jiàn)的內(nèi)容,可能會(huì)出現(xiàn)“滾動(dòng)過(guò)快導(dǎo)致白屏”。解決辦法是:在上下區(qū)域額外渲染一些數(shù)據(jù)(比如上下各多渲染 5 條),即所謂“緩沖區(qū)”。
  3. 占位高度(位置計(jì)算):用戶看到的只是局部,但滾動(dòng)條必須是全量的。

通常做法:用一個(gè)虛擬的容器高度(總數(shù)據(jù)條數(shù) × 每條高度)來(lái)?yè)纹饾L動(dòng)條。然后通過(guò) transform: translateY(...) 或 margin-top 來(lái)調(diào)整渲染元素的位置,看起來(lái)就像“在滾動(dòng)”。

工作流程拆解

  1. 用戶滾動(dòng)時(shí),計(jì)算當(dāng)前的 scrollTop。
  2. 根據(jù) scrollTop 推算出 起始索引(startIndex) 和 結(jié)束索引(endIndex)。
  3. 截取 listData[startIndex ~ endIndex] 作為 渲染區(qū)數(shù)據(jù)
  4. 用一個(gè)大容器元素模擬總高度,再通過(guò) translateY(offsetY) 把可見(jiàn)內(nèi)容放到正確的位置。

這樣,不管列表有 5 千條還是 5 萬(wàn)條,瀏覽器永遠(yuǎn)只需要渲染幾十個(gè) DOM 節(jié)點(diǎn),性能從根本上被優(yōu)化。

實(shí)例代碼

最后咱們就以 Vue 為例,來(lái)看下如何實(shí)現(xiàn)這個(gè)虛擬列表方案

VirtualList.vue

<script setup>
import { ref, computed, onMounted, nextTick, watch, defineExpose } from 'vue'

const props = defineProps({
 items: { type: Array, required: true }, // 全量數(shù)據(jù)
 height: { type: Number, required: true }, // 容器高度
 itemHeight: { type: Number, required: true }, // 每行固定高度
 buffer: { type: Number, default: 6 }, // 緩沖條數(shù)
 keyField: { type: String, default: 'id' } // 唯一 key
})

const emit = defineEmits(['rangeChange', 'reachEnd'])

const containerRef = ref(null)
const scrollTop = ref(0)

const visibleCount = computed(() => Math.ceil(props.height / props.itemHeight))
const totalHeight = computed(() => props.items.length * props.itemHeight)

const startIndex = computed(() =>
 Math.max(0, Math.floor(scrollTop.value / props.itemHeight) - props.buffer)
)
const endIndex = computed(() =>
 Math.min(
  props.items.length,
  startIndex.value + visibleCount.value + props.buffer * 2
 )
)

const offsetY = computed(() => startIndex.value * props.itemHeight)
const visibleItems = computed(() =>
 props.items.slice(startIndex.value, endIndex.value)
)

function onScroll() {
 const el = containerRef.value
 if (!el) return
 scrollTop.value = el.scrollTop
 emit('rangeChange', { start: startIndex.value, end: endIndex.value })
 if (endIndex.value >= props.items.length - props.buffer * 2) emit('reachEnd')
}

function scrollToIndex(index, align = 'start') {
 const el = containerRef.value
 if (!el) return
 const clamped = Math.max(0, Math.min(index, props.items.length - 1))
 let top = clamped * props.itemHeight
 if (align === 'center') top -= (props.height - props.itemHeight) / 2
 else if (align === 'end') top -= props.height - props.itemHeight
 el.scrollTop = Math.max(0, top)
 onScroll()
}

function reset() {
 const el = containerRef.value
 if (!el) return
 el.scrollTop = 0
 onScroll()
}

onMounted(() => nextTick(onScroll))
watch(
 () => props.items.length,
 async () => {
  await nextTick()
  onScroll()
 }
)

defineExpose({ scrollToIndex, reset })
</script>

<template>
 <div
  ref="containerRef"
  class="vl-container"
  :style="{ height: height + 'px' }"
  @scroll="onScroll"
 >
  <!-- 占位高度:撐滾動(dòng)條 -->
  <div :style="{ height: totalHeight + 'px' }" aria-hidden="true"></div>

  <!-- 可視區(qū):絕對(duì)定位 + translateY -->
  <div class="vl-list" :style="{ transform: `translateY(${offsetY}px)` }">
   <div
    v-for="(row, i) in visibleItems"
    :key="row?.[keyField] ?? startIndex + i"
    class="vl-item"
    :style="{ height: itemHeight + 'px', lineHeight: itemHeight + 'px' }"
   >
    <slot name="row" :row="row" :index="startIndex + i"></slot>
   </div>
  </div>
 </div>
</template>

<style>
.vl-container {
 /* 父容器必須相對(duì)定位 + 可滾動(dòng) */
 position: relative;
 overflow-y: auto;
 border: 1px solid #e5e7eb;
 background: #fff;
}

/* 關(guān)鍵:絕對(duì)定位到頂部 + 蓋住占位層 */
.vl-list {
 position: absolute;
 top: 0;
 left: 0;
 right: 0;
 width: 100%;
 z-index: 1;
 will-change: transform;
}

.vl-item {
 padding: 0 12px;
 border-bottom: 1px solid #f1f5f9;
 font-size: 14px;
 white-space: nowrap;
 text-overflow: ellipsis;
 overflow: hidden;
}
</style>

App.vue

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

const items = ref(
 Array.from({ length: 50000 }, (_, i) => ({
  id: i,
  name: `訂單 #${i}`,
  amount: (Math.random() * 1000).toFixed(2)
 }))
)

function onRangeChange(r) {
 console.log('可見(jiàn)區(qū)范圍:', r)
}

function onReachEnd() {
 console.log('觸底了,加載更多數(shù)據(jù)...')
 setTimeout(() => {
  const base = items.value.length
  items.value.push(
   ...Array.from({ length: 1000 }, (_, i) => ({
    id: base + i,
    name: `訂單 #${base + i}`,
    amount: (Math.random() * 1000).toFixed(2)
   }))
  )
 }, 500)
}
</script>

<template>
 <VirtualList
  :items="items"
  :height="560"
  :item-height="44"
  :buffer="8"
  key-field="id"
  @rangeChange="onRangeChange"
  @reachEnd="onReachEnd"
 >
  <template #row="{ row, index }">
   <span style="margin-right: 8px">{{ index }}</span>
   {{ row.name }} -- ¥{{ row.amount }}
  </template>
 </VirtualList>
</template>

最終渲染效果

圖片圖片

責(zé)任編輯:武曉燕 來(lái)源: 程序員Sunday
相關(guān)推薦

2019-07-16 08:51:03

熱搜新浪微博數(shù)據(jù)

2021-11-02 14:46:50

數(shù)據(jù)

2023-10-19 15:13:25

2019-11-28 18:54:50

數(shù)據(jù)庫(kù)黑客軟件

2011-03-31 11:24:14

數(shù)據(jù)搜索本文字段

2018-08-27 07:01:33

數(shù)據(jù)分析數(shù)據(jù)可視化租房

2022-04-28 20:12:44

二分法搜索算法

2017-07-22 22:11:36

數(shù)據(jù)丟失操作

2022-06-17 10:15:35

面試API前端

2018-09-13 09:39:03

騰訊運(yùn)維IT

2015-10-08 08:51:40

PHP內(nèi)存耗盡解決方案

2024-05-11 12:34:51

EasyExcelOOM代碼

2024-04-09 07:56:36

MySQL數(shù)據(jù)性能

2019-10-18 15:36:24

網(wǎng)易歌單熱評(píng)

2024-03-07 08:08:51

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

2022-10-27 21:32:28

數(shù)據(jù)互聯(lián)網(wǎng)數(shù)據(jù)中心

2014-01-21 17:36:58

2025-03-07 11:17:09

2013-05-16 10:15:11

信息泄密彭博Bloomberg

2022-04-12 16:39:55

數(shù)據(jù)泄露網(wǎng)絡(luò)攻擊
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)