亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Vue高性能列表GridList組件及實現(xiàn)思路詳解

 更新時間:2023年11月07日 09:41:40   作者:youth君  
這篇文章主要為大家介紹了Vue高性能列表GridList組件及實現(xiàn)思路詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

GridList列表組件及實現(xiàn)思路

列表是一種常見的UI組件,相信大家應該都遇到過,并且也都自己實現(xiàn)過!不知道大家是怎么實現(xiàn)的,是根據(jù)業(yè)務進行CSS布局還是使用了第三方的組件。

在這里分享下自認為比較舒適的列表組件及實現(xiàn)思路。

放心食用:GridList高性能列表體驗地址

使用及效果

網(wǎng)格列表

代碼

<script setup lang="ts">
import GridList, { RequestFunc } from '@/components/GridList.vue';
const data: RequestFunc<number> = ({ page, limit }) => {
  return new Promise((resolve) => {
    console.log('開始加載啦', page, limit);
    setTimeout(() => {
      resolve({
        data: Array.from({ length: limit }, (_, index) => index + (page - 1) * limit),
        total: 500,
      });
    }, 1000);
  });
};
</script>
<template>
  <GridList :request="data" :column-gap="20" :row-gap="20" :limit="100" :item-min-width="200" class="grid-list">
    <template #empty>
      <p>暫無數(shù)據(jù)</p>
    </template>
    <template #default="{ item }">
      <div class="item">{{ item }}</div>
    </template>
    <template #loading>
      <p>加載中...</p>
    </template>
    <template #noMore>
      <p>沒有更多了</p>
    </template>
  </GridList>
</template>

行列表

實現(xiàn)行列表只需要將item-min-width屬性配置為100%,即表示每個item最小寬度為容器寬度。

代碼

<script setup lang="ts">
import GridList, { RequestFunc } from '@/components/GridList.vue';
const data: RequestFunc<number> = ({ page, limit }) => {
  return new Promise((resolve) => {
    console.log('開始加載啦', page, limit);
    setTimeout(() => {
      resolve({
        data: Array.from({ length: limit }, (_, index) => index + (page - 1) * limit),
        total: 500,
      });
    }, 1000);
  });
};
</script>
<template>
  <GridList :request="data" :column-gap="20" :row-gap="20" :limit="100" item-min-width="100%" class="grid-list">
    <template #empty>
      <p>暫無數(shù)據(jù)</p>
    </template>
    <template #default="{ item }">
      <div class="item">{{ item }}</div>
    </template>
    <template #loading>
      <p>加載中...</p>
    </template>
    <template #noMore>
      <p>沒有更多了</p>
    </template>
  </GridList>
</template>

實現(xiàn)思路

網(wǎng)格布局

我們創(chuàng)建了一個名為GridList的組件,用于展示網(wǎng)格卡片的效果。該組件的主要功能是處理網(wǎng)格布局,而不關心卡片的具體內(nèi)容。

GridList組件通過data-source屬性接收數(shù)據(jù)。為了實現(xiàn)響應式布局,我們還提供了一些輔助屬性,如item-min-width、item-min-height、row-gapcolumn-gap

<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
const props = defineProps<{
  dataSource?: any[];
  itemMinWidth?: number;
  itemMinHeight?: number;
  rowGap?: number;
  columnGap?: number;
}>();
const data = ref<any[]>([...props.dataSource]);
</script>
<template>
  <div ref="containerRef" class="infinite-list-wrapper">
    <div v-else class="list">
      <div v-for="(item, index) in data" :key="index">
        <slot :item="item" :index="index">
          {{ item }}
        </slot>
      </div>
    </div>
  </div>
</template>
<style lang="scss" scoped>
.infinite-list-wrapper {
  text-align: center;
  overflow-y: scroll;
  position: relative;
  -webkit-overflow-scrolling: touch;
  .list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(calc(v-bind(itemMinWidth) * 1px), 1fr));
    grid-auto-rows: minmax(auto, calc(v-bind(itemMinHeight) * 1px));
    column-gap: calc(v-bind(columnGap) * 1px);
    row-gap: calc(v-bind(rowGap) * 1px);
    div:first-of-type {
      grid-column-start: 1;
      grid-column-end: 1;
    }
  }
}
</style>

實現(xiàn)響應式網(wǎng)格布局的關鍵點如下:

  • 使用 display: grid; 將 .list 元素設置為網(wǎng)格布局。
  • grid-template-columns 屬性創(chuàng)建了自適應的列布局。使用 repeat(auto-fill, minmax(...)) 表示根據(jù)容器寬度自動填充列,并指定每列的最小和最大寬度。
  • grid-auto-rows 屬性創(chuàng)建了自適應的行布局。使用 minmax(auto, ...) 表示根據(jù)內(nèi)容自動調(diào)整行高度。
  • column-gap 和 row-gap 屬性設置了網(wǎng)格項之間的列間距和行間距。

分頁加載

盡管我們的組件能夠滿足設計要求,但面臨的最明顯問題是處理大量數(shù)據(jù)時的效率問題。隨著數(shù)據(jù)量的增加,接口響應速度變慢,頁面可能出現(xiàn)白屏現(xiàn)象,因為 DOM 元素太多。

這時候,后端團隊提出了一個合理的疑問(BB)??:難道我們不能進行分頁查詢嗎?我們需要聯(lián)合多個表進行數(shù)據(jù)組裝,這本身就很耗時啊...

確實,他們說得有道理。為了解決這個問題,我們需要在不改變交互方式的情況下實現(xiàn)數(shù)據(jù)的分頁查詢。

以前,GridList 組件的數(shù)據(jù)是通過 data-source 屬性傳遞給它的,由組件的使用方進行數(shù)據(jù)處理和傳遞。但如果每個使用 GridList 的頁面都要自己處理分頁邏輯,那會變得非常麻煩。

為了提供更舒適的組件使用體驗,我們決定在 GridList 組件內(nèi)部完成分頁邏輯。無論數(shù)據(jù)如何到達,對于 GridList 組件來說,都是通過函數(shù)調(diào)用的方式進行數(shù)據(jù)獲取。為此,我們引入了一個新的屬性 request,用于處理分頁邏輯。

通過這樣的改進,我們可以在不影響現(xiàn)有交互方式的前提下,讓 GridList 組件自己處理數(shù)據(jù)分頁,從而提升整體的使用便捷性。

request 接受一個類型為 RequestFunc 的函數(shù),該函數(shù)的定義如下:

export interface Pagination {
  limit: number;
  page: number;
}
export interface RequestResult<T> {
  data: T[];
  total: number;
}
export type RequestFunc<T> = (pagination: Pagination) => Promise<RequestResult<T>> | RequestResult<T>;

通過使用 request 函數(shù),使用方無需手動維護 data 數(shù)據(jù)或處理分頁邏輯?,F(xiàn)在只需將數(shù)據(jù)獲取邏輯封裝到 request 函數(shù)中。

一旦滾動條滾動到底部,就會觸發(fā) props.request 函數(shù)來獲取數(shù)據(jù),實現(xiàn)滾動分頁加載的效果。

這樣的改進使得使用方能夠?qū)W⒂跀?shù)據(jù)獲取邏輯,并將其封裝到 request 函數(shù)中。不再需要手動管理數(shù)據(jù)和分頁邏輯,簡化了使用方式,使得整體體驗更加簡潔和便捷。

<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
const props = defineProps<{
    request?: RequestFunc<any>;
    limit?: number;
    loadDistance?: number;
    //...原有props
  }>();
const containerRef = ref<HTMLDivElement>();
const loading = ref<boolean>(false);
const data = ref<any[]>([]);
const total = ref<number>(0);
const page = ref<number>(1);
/** 沒有更多了 */
const noMore = computed<boolean>(
  () => total.value === 0 || data.value.length >= total.value || data.value.length < props.limit
);
//... watch處理
function handleScroll(event: Event) {
  event.preventDefault();
  const container = event.target as HTMLDivElement;
  const canLoad =
    container.scrollTop + container.clientHeight >= container.scrollHeight - props.loadDistance &&
    !loading.value &&
    !noMore.value;
  if (canLoad) {
    load();
  }
}
async function load() {
  loading.value = true;
  const result = await Promise.resolve(
    props.request({
      limit: props.limit,
      page: page.value,
    })
  );
  total.value = result.total;
  data.value.push(...result.data);
  if (!noMore.value) {
    page.value = page.value + 1;
  }
  loading.value = false;
}
</script>

虛擬列表

除了添加 request 屬性以實現(xiàn)分頁加載數(shù)據(jù),我們還需要進一步優(yōu)化。盡管這種懶加載的分頁加載可以解決網(wǎng)絡請求和首屏加載的問題,但隨著數(shù)據(jù)增加,DOM 元素的數(shù)量也會不斷增加,可能導致頁面出現(xiàn)卡頓的情況。

為了解決這個問題,我們可以引入虛擬列表的概念和實現(xiàn)方法。虛擬列表的原理和實現(xiàn)思路已經(jīng)在網(wǎng)上有很多資料,這里就不再贅述。

虛擬列表的主要目標是解決列表渲染性能問題,并解決隨著數(shù)據(jù)增加而導致的 DOM 元素過多的問題。

虛擬列表的關鍵在于計算出當前可視區(qū)域的數(shù)據(jù)起始索引 startIndex 和終點索引 endIndex。GridList 組件本身并不需要關心計算的具體過程,只需要獲得 startIndex 和 endIndex 即可。因此,我們可以將虛擬列表的計算邏輯封裝成一個自定義 Hook,該 Hook 的作用就是計算當前可視區(qū)域的 startIndex 和 endIndex ???。

通過這樣的優(yōu)化,我們能夠更好地處理大量數(shù)據(jù)的渲染問題,提升頁面的性能和流暢度。同時,GridList 組件無需關心具體的計算過程,只需要使用計算得到的 startIndex 和 endIndex 即可 ????。

useVirtualGridList

在虛擬列表中,只渲染可視區(qū)域的 DOM 元素,為了實現(xiàn)滾動效果,我們需要一個隱藏的 DOM 元素,并將其高度設置為列表的總高度。

已知屬性:

  • containerWidth: 容器寬度,通過 container.clientWidth 獲取
  • containerHeight: 容器高度,通過 container.clientHeight 獲取
  • itemMinWidth: item 最小寬度,通過 props.itemMinWidth 獲取
  • itemMinHeight: item 最小高度,通過 props.itemMinHeight 獲取
  • columnGap: item 的列間距,通過 props.columnGap 獲取
  • rowGap: item 的行間距,通過 props.rowGap 獲取
  • data: 渲染數(shù)據(jù)列表,通過 props.dataSource/props.request 獲取
  • scrollTop: 滾動條偏移量,通過 container.addEventListener('scroll', () => {...}) 獲取

計算屬性:

  • 渲染列數(shù) columnNumMath.floor((containerWidth - itemMinWidth) / (itemMinWidth + columnGap)) + 1
  • 渲染行數(shù) rowNumMath.ceil(data.length / columnNum)
  • 列表總高度 listHeightMath.max(rowNum * itemMinHeight + (rowNum - 1) * rowGap, 0)
  • 可見行數(shù) visibleRowNumMath.ceil((containerHeight - itemMinHeight) / (itemMinHeight + rowGap)) + 1
  • 可見 item 數(shù) visibleCountvisibleRowNum * columnNum
  • 起始索引 startIndexMath.ceil((scrollTop - itemMinHeight) / (itemMinHeight + rowGap)) * columnNum
  • 終點索引 endIndexstartIndex + visibleCount
  • 列表偏移位置 startOffsetscrollTop - (scrollTop % (itemMinHeight + rowGap))

通過以上計算,我們可以根據(jù)容器尺寸、item 最小尺寸、間距和滾動條位置來計算出虛擬列表的相關參數(shù),以便準確渲染可見區(qū)域的數(shù)據(jù)。這樣的優(yōu)化能夠提升列表的渲染性能,并確保用戶在滾動時獲得平滑的體驗。

//vue依賴引入
export const useVirtualGridList = ({
  containerRef,
  itemMinWidth,
  itemMinHeight,
  rowGap,
  columnGap,
  data,
}: VirtualGridListConfig) => {
  const phantomElement = document.createElement('div');
  //...phantomElement布局
  const containerHeight = ref<number>(0);
  const containerWidth = ref<number>(0);
  const startIndex = ref<number>(0);
  const endIndex = ref<number>(0);
  const startOffset = ref<number>(0);
  /** 計算列數(shù) */
  const columnNum = computed<number>(
    () => Math.floor((containerWidth.value - itemMinWidth.value) / (itemMinWidth.value + columnGap.value)) + 1
  );
  /** 計算行數(shù) */
  const rowNum = computed<number>(() => Math.ceil(data.value.length / columnNum.value));
  /** 計算總高度 */
  const listHeight = computed<number>(() =>
    Math.max(rowNum.value * itemMinHeight.value + (rowNum.value - 1) * rowGap.value, 0)
  );
  /** 可見行數(shù) */
  const visibleRowNum = computed<number>(
    () => Math.ceil((containerHeight.value - itemMinHeight.value) / (itemMinHeight.value + rowGap.value)) + 1
  );
  /** 可見item數(shù)量 */
  const visibleCount = computed<number>(() => visibleRowNum.value * columnNum.value);
  watch(
    () => listHeight.value,
    () => {
      phantomElement.style.height = `${listHeight.value}px`;
    }
  );
  watchEffect(() => {
    endIndex.value = startIndex.value + visibleCount.value;
  });
  const handleContainerResize = () => {
    nextTick(() => {
      if (containerRef.value) {
        containerHeight.value = containerRef.value.clientHeight;
        containerWidth.value = containerRef.value.clientWidth;
      }
    });
  };
  const handleScroll = () => {
    if (!containerRef.value) {
      return;
    }
    const scrollTop = containerRef.value.scrollTop;
    const startRowNum = Math.ceil((scrollTop - itemMinHeight.value) / (itemMinHeight.value + rowGap.value));
    /** 計算起始索引 */
    startIndex.value = startRowNum * columnNum.value;
    /** 計算內(nèi)容偏移量 */
    startOffset.value = scrollTop - (scrollTop % (itemMinHeight.value + rowGap.value));
  };
  onMounted(() => {
    if (containerRef.value) {
      containerRef.value.appendChild(phantomElement);
      containerRef.value.addEventListener('scroll', (event: Event) => {
        event.preventDefault();
        handleScroll();
      });
      handleScroll();
    }
  });
  return { startIndex, endIndex, startOffset, listHeight };
};

這段代碼實現(xiàn)了虛擬網(wǎng)格列表的核心邏輯,通過監(jiān)聽容器的滾動和大小改變事件,實現(xiàn)了僅渲染可見區(qū)域的列表項,從而提高性能。??

在代碼中,我們創(chuàng)建了一個 phantomElement 占位元素,其高度被設置為列表的總高度,以確保滾動條的滾動范圍與實際列表的高度一致。這樣,在滾動時,我們可以根據(jù)滾動位置動態(tài)計算可見區(qū)域的起始和結(jié)束索引,并只渲染可見的列表項,避免了不必要的 DOM 元素渲染,從而提升了性能。??

在代碼中,phantomElement 被創(chuàng)建為絕對定位的元素,并設置了其位置屬性和高度。通過 watch 監(jiān)聽器,它的高度會根據(jù)列表的總高度進行更新,以保持與實際列表的高度一致。??

通過利用占位元素,我們成功實現(xiàn)了虛擬列表的滾動渲染,減少了不必要的 DOM 元素渲染,從而顯著提升了用戶體驗和性能表現(xiàn)。???

GridList中使用useVirtualGridList:

<script lang="ts" setup>
import { computed, ref, watch } from 'vue';
import { useVirtualGridList } from '@/hooks/useVirtualGridList';
//...其他代碼
/** 計算最小寬度的像素值 */
const itemMinWidth = computed<number>(() => props.itemMinWidth);
/** 計算最小高度的像素值 */
const itemMinHeight = computed<number>(() => props.itemMinHeight);
/** 計算列間距的像素值 */
const columnGap = computed<number>(() => props.columnGap);
/** 計算行間距的像素值 */
const rowGap = computed<number>(() => props.rowGap);
/** 計算虛擬列表的起始/終止索引 */
const { startIndex, endIndex, startOffset, listHeight } = useVirtualGridList({
  containerRef,
  data,
  itemMinWidth,
  itemMinHeight,
  columnGap,
  rowGap,
});
//...其他代碼
</script>
<template>
  <div ref="containerRef" class="infinite-list-wrapper" @scroll="handleScroll">
    <div v-if="data.length === 0 && !loading">
      <slot name="empty">No Data</slot>
    </div>
    <div v-else class="list">
      <div v-for="(item, index) in data.slice(startIndex, endIndex)" :key="index">
        <slot :item="item" :index="index">
          {{ item }}
        </slot>
      </div>
    </div>
    <div v-if="loading" class="bottom">
      <slot name="loading"></slot>
    </div>
    <div v-if="noMore && data.length > 0" class="bottom">
      <slot name="noMore"></slot>
    </div>
  </div>
</template>

性能展示

虛擬列表

一次性加載十萬條數(shù)據(jù)

懶加載+虛擬列表

分頁加載,每頁加載一萬條

GitHub源碼地址

以上就是Vue高性能列表GridList組件及實現(xiàn)思路詳解的詳細內(nèi)容,更多關于Vue GridList列表組件的資料請關注腳本之家其它相關文章!

相關文章

  • vue 動態(tài)綁定背景圖片的方法

    vue 動態(tài)綁定背景圖片的方法

    這篇文章主要介紹了vue 動態(tài)綁定背景圖片的方法,在文末給大家介紹了vue如何給v-for循環(huán)的標簽添加背景圖片,需要的朋友參考下吧
    2018-08-08
  • vue3表單輸入綁定方式

    vue3表單輸入綁定方式

    這篇文章主要介紹了vue3表單輸入綁定方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • vue組件講解(is屬性的用法)模板標簽替換操作

    vue組件講解(is屬性的用法)模板標簽替換操作

    這篇文章主要介紹了vue組件講解(is屬性的用法)模板標簽替換操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • 使用form-create動態(tài)生成vue自定義組件和嵌套表單組件

    使用form-create動態(tài)生成vue自定義組件和嵌套表單組件

    這篇文章主要介紹了使用form-create動態(tài)生成vue自定義組件和嵌套表單組件,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-01-01
  • VUE使用day.js顯示時分秒并實時更新時間效果實例

    VUE使用day.js顯示時分秒并實時更新時間效果實例

    vue.js是目前比較流行的前端框架之一,它提供了非常多的基礎組件和工具庫,以方便開發(fā)者快速搭建具有可重用性的web應用,下面這篇文章主要給大家介紹了關于VUE使用day.js顯示時分秒并實時更新時間效果的相關資料,需要的朋友可以參考下
    2024-04-04
  • vue3 Class 與 Style 綁定操作方法

    vue3 Class 與 Style 綁定操作方法

    數(shù)據(jù)綁定的一個常見需求場景是操縱元素的 CSS class 列表和內(nèi)聯(lián)樣式,因為 class 和 style 都是 attribute,我們可以和其他 attribute 一樣使用 v-bind 將它們和動態(tài)的字符串綁定,這篇文章主要介紹了vue3 Class 與 Style 綁定操作方法,需要的朋友可以參考下
    2024-05-05
  • vue.js實現(xiàn)備忘錄demo

    vue.js實現(xiàn)備忘錄demo

    這篇文章主要為大家詳細介紹了vue.js實現(xiàn)備忘錄的相關代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-06-06
  • 淺談vue中關于checkbox數(shù)據(jù)綁定v-model指令的個人理解

    淺談vue中關于checkbox數(shù)據(jù)綁定v-model指令的個人理解

    這篇文章主要介紹了淺談vue中關于checkbox數(shù)據(jù)綁定v-model指令的個人理解,v-model用于表單的數(shù)據(jù)綁定很常見,下面就來詳細的介紹一下
    2018-11-11
  • 怎樣在vue項目下添加ESLint的方法

    怎樣在vue項目下添加ESLint的方法

    這篇文章主要介紹了怎樣在vue項目下添加ESLint的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2019-05-05
  • 手把手教你Vue-cli項目的搭建

    手把手教你Vue-cli項目的搭建

    這篇文章主要為大家詳細介紹了Vue-cli項目的搭建方法,文中圖片介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-02-02

最新評論