Vue3|性能優(yōu)化之虛擬列表

RecycleScroller虛擬列表的實(shí)戰(zhàn)。

背景

當(dāng)渲染大型列表時(shí),由于瀏覽器需要處理大量的 DOM 節(jié)點(diǎn),會(huì)帶來頁(yè)面卡頓、內(nèi)存占用過高等問題。此時(shí)可以通過將列表虛擬化技術(shù),即只渲染可見區(qū)域內(nèi)的數(shù)據(jù)項(xiàng),而不是全部渲染,且在滾動(dòng)過程中移除舊的添加新的,這樣無論列表有多少項(xiàng),只會(huì)有一小部分在DOM中,從而提高性能和內(nèi)存效率、保持流暢的滾動(dòng)體驗(yàn)。

vue-virtual-scroller正是一個(gè)實(shí)現(xiàn)了列表和表格虛擬滾動(dòng)的社區(qū)庫(kù),主要提供兩個(gè)組件:

  • RecycleScroller :一個(gè)基礎(chǔ)的虛擬滾動(dòng)列表,需要item高度是固定的
  • DynamicScroller :一個(gè)更高級(jí)的虛擬滾動(dòng)列表,可以處理動(dòng)態(tài)的item高度

這里以RecycleScroller為例,先安裝和導(dǎo)入:

// 安裝
npm install --save vue-virtual-scroller@next
// 導(dǎo)入RecycleScroller
import { RecycleScroller } from "vue-virtual-scroller";
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
app.component("RecycleScroller", RecycleScroller);

RecycleScroller實(shí)戰(zhàn)

RecycleScroller提供很多屬性,主要使用到以下四個(gè)核心屬性,接下來依次解釋:

<RecycleScroller
  v-slot="{ item, index, active }"
  :items="items"
  :item-size=""
  key-field=""
>
  <div class="item-root">
    ...
  </div>
</RecycleScroller>
v-slot

當(dāng)前渲染item的相關(guān)信息,提供信息包括:

  • item:當(dāng)前渲染的item數(shù)據(jù),即items[index]
  • index: 當(dāng)前渲染的item所在位置
  • active:當(dāng)前渲染的item是否可見
items

必填,渲染的列表數(shù)據(jù)源。注意,如果數(shù)據(jù)源有變化,比如上拉刷新、下拉加載更多,此時(shí)需要修改items屬性,否則RecycleScroller不會(huì)更新items,參考官方demo的做法:

const items = computed(() => {
  return props.list.map((item) =>
    Object.assign({}, { random: Math.random() }, item),
  );
});
item-size

必填,item的高度。若有必要需適配手機(jī)端和網(wǎng)頁(yè)端。

key-field

關(guān)鍵屬性名,這個(gè)屬性是item唯一標(biāo)識(shí),默認(rèn)名稱是id。一般在數(shù)據(jù)會(huì)有提供如key、id、index這樣的屬性,如果都沒有的話,需要對(duì)源數(shù)據(jù)進(jìn)行加工補(bǔ)充一個(gè)id屬性。如果不存在這個(gè)屬性或?qū)傩灾挡晃ㄒ唬瑫?huì)發(fā)現(xiàn)列表存在空白項(xiàng),即渲染異常的item。

const items = computed(() => {
  return props.list.map((item, index) =>
    Object.assign({}, { id: `test-${index}` }, item),
  );
});

實(shí)際應(yīng)用一下,現(xiàn)在有這樣一個(gè)長(zhǎng)列表,可以看到dom樹很長(zhǎng):


相關(guān)代碼如下:

<div class="output-content-container">
  <div
    v-for="(item) in props.data"
    class="output-item-root"
  >
    ...
  </div>
</div>

.output-content-container {
  width: 100%;
  display: flex;
  flex-direction: column;
}

改用RecycleScroller僅需要改幾行代碼:

 <RecycleScroller
  v-slot="{ item }"
  class="output-content-container"
  :item-size="isMobileRef ? 100 : 75"
  :items="items"
>
  <div class="output-item-root">
    ...
  </div>
 </RecycleScroller>
 
 const items = computed(() => {
  return props.data.map((item, index) =>
    Object.assign({}, { id: `data-id-${index}` }, item),
  );
});
 
.output-content-container {
  width: 100%;
  max-height: 100vh;
}

需要強(qiáng)調(diào)的一點(diǎn)是,RecycleScroller的可視高度一定是可知的,比如這里設(shè)置了最大高度和屏幕高度一樣,否則它無法計(jì)算出來哪些列表項(xiàng)是在可視區(qū)內(nèi)的,會(huì)認(rèn)為全部能展示,導(dǎo)致復(fù)用能力失效。改完后能成功復(fù)用了:

template

在這個(gè)例子中還有一處細(xì)節(jié),就是滑動(dòng)效果發(fā)生變化了,可以看到原來只有一個(gè)頁(yè)面整體的滑動(dòng)條(右),現(xiàn)在內(nèi)部多了一個(gè)的滑動(dòng)條(左):


這個(gè)很好理解,RecycleScroller本身也要知道渲染視圖和滾動(dòng)位置,如果想和原有保持一致,也就是將列表上方的信息和列表拼接一起成為一個(gè)整體,只要將頭部信息填空到#before中即可,代碼如下:

<RecycleScroller
  class="output-content-container"
  :item-size="isMobileRef ? 100 : 75"
  :items="items"
>
  <template #before>
    ...
  </template>

  <template v-slot="{ item }">
    <div class="output-item-root">
      ...
    </div>
  </template>
</RecycleScroller>
網(wǎng)格布局

RecycleScroller還可以實(shí)現(xiàn)網(wǎng)格布局,還要涉及兩個(gè)關(guān)鍵屬性:

  • item-secondary-size:item寬度,未設(shè)置時(shí)會(huì)使用item-size的值
  • grid-items:一行展示的個(gè)數(shù),即列數(shù)

比如優(yōu)化這樣一個(gè)網(wǎng)格列表:


修改前:

<div class="content-gridview">
  <div
    v-for="(item, index) in items"
    class="content-grid-item"
    @click="onChooseItem(item)"
  >
    ... 
  </div>
</div>

.content-gridview {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  grid-gap: 10px;
  max-height: 600px;
}

修改后:

<RecycleScroller
  v-slot="{ item, index }"
  :item-secondary-size="360"
  :item-size="90"
  :items="items"
  :grid-items="2"
  key-field="indexId"
  class="content-gridview"
>
  <div class="content-grid-item" @click="onChooseItem(item)">
    ... 
  </div>
</RecycleScroller>

.content-gridview {
  max-height: 600px;
}
.content-grid-item {
  ...
  margin-right: 10px;
}

由于不支持設(shè)置grid-gap,這里通過給item設(shè)置右間距方式實(shí)現(xiàn)的,最終效果:

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1、vue3內(nèi)部使用了 Tree-shaking 技術(shù),沒有使用到的api,在打包時(shí)不會(huì)進(jìn)行打包,直接進(jìn)行移除了。...
    焚心123閱讀 2,337評(píng)論 0 0
  • 前端性能優(yōu)化 提升首屏的加載速度,是前端性能優(yōu)化中最重要的環(huán)節(jié),這里筆者梳理出一些 常規(guī)且有效 的首屏優(yōu)化建議 目...
    kkgo_閱讀 498評(píng)論 1 1
  • 前言 Vue 框架通過數(shù)據(jù)雙向綁定和虛擬 DOM 技術(shù),幫我們處理了前端開發(fā)中最臟最累的 DOM 操作部分, 我們...
    前端小碼農(nóng)呀閱讀 320評(píng)論 0 0
  • 隨著現(xiàn)代Web應(yīng)用的復(fù)雜性增加,性能優(yōu)化變得至關(guān)重要。Vue 3作為一款流行的前端框架,為開發(fā)者提供了一系列優(yōu)化技...
    樂天派藝術(shù)家閱讀 1,556評(píng)論 0 0
  • Vue 項(xiàng)目性能優(yōu)化 — 實(shí)踐指南(網(wǎng)上最全 / 詳細(xì)) 前言 Vue 框架通過數(shù)據(jù)雙向綁定和虛擬 DOM 技術(shù),...
    Jiao_0805閱讀 282評(píng)論 0 0

友情鏈接更多精彩內(nèi)容