欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue簡易版無限加載組件實現(xiàn)原理與示例代碼

 更新時間:2022年07月04日 08:46:14   作者:kzkz  
這篇文章主要給大家介紹了關(guān)于Vue簡易版無限加載組件實現(xiàn)原理與示例代碼的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用vue具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下

背景

遇到的兩個問題:scroll 事件不觸發(fā)、如何將 loading 狀態(tài)放在無限加載組件中進行管理。

無限加載組件在展示列表頁數(shù)據(jù)時比較常見。特別是在 H5 列表頁中,數(shù)據(jù)比較多,需要做分頁,無限加載組件就是一個非常好的選擇。

當(dāng)列表頁數(shù)據(jù)比較多時,一次性從服務(wù)端拿到所有的數(shù)據(jù)會比較耗時,長時間不展示列表數(shù)據(jù),比較影響用戶體驗。所以對于一般的長列表數(shù)據(jù),都會做分頁。

首次請求時,只請求第一頁數(shù)據(jù);當(dāng)用戶上拉即將到達列表底部時,再請求下一頁數(shù)據(jù),將下一頁數(shù)據(jù)拼接在之前的列表后。

實現(xiàn)功能

使用 vue3 composition API 實現(xiàn)如下功能:

  • InfinitView 組件:將 InfinitView 組件包裹在列表(項)外面即可實現(xiàn)無限加載。
  • 節(jié)流加載:每次觸底加載時,會自動節(jié)流,同一頁數(shù)據(jù)只會請求一次(如果請求成功)。

注意:InfinitView 直接子元素高度需要比 InfinitView 組件高,才會觸發(fā)滾動加載。InfinitView 組件的高度默認為其父元素的 100%。

Props

// 觸底距離,當(dāng)距底部距離小于等于 distance 時,會觸發(fā)加載函數(shù)
distance: {
  type: Number,
  default: 30,
},
// 加載函數(shù),觸底時執(zhí)行
onload: {
  type: Function,
  default: async () => {},
},
// 行內(nèi)樣式,在外部可以通過 classStyle 改變 InfinitView 組件的樣式
classStyle: {
  type: Object,
  default: () => ({}),
},

使用

直接將 InfiniteView 組件包裹在列表項外面即可:

<InfiniteView :onload="onload">
  <div v-for="item in list">
    {{ item }}
  </div>
</InfiniteView>

使用 setTimeout 模擬列表數(shù)據(jù)的加載:

// nextPage 表示下一次請求哪一頁的數(shù)據(jù)
const nextPage = ref(1);
// list 表示數(shù)據(jù)列表
const list = ref(new Array(30).fill(0));

const onload = () => {
  return new Promise((resolve) => {
    setTimeout(() => {
      list.value.push(...new Array(50).fill(0).map((_, i) => i + 1 + (nextPage.value - 1) * 50));
      nextPage.value++;
      resolve(nextPage.value);
    }, 200);
  });
}

使用時需要注意:InfinitView 組件的高度默認為其父元素的 100%,如果其父元素高度不確定(例如:由子元素撐開),會導(dǎo)致 InfinitView 無法監(jiān)聽到滾動事件,也就不會觸發(fā) onload 函數(shù)(后面會解釋原因)。

解決方案有兩種

  • 為 InfinitView 組件的父元素設(shè)置一個可計算的高度。
  • 為 InfinitView 組件設(shè)置一個可計算的高度,可通過其 props 行內(nèi)樣式 classStyle 設(shè)置,或者在外部給 InfinitView 組件加上類名及其樣式。

注意:這里的可計算高度可以是:由 flex 彈性容器計算得來,但不能由子元素(InfinitView)撐開得來。

組件實現(xiàn)

組件的實現(xiàn)非常簡單,InfinitView 組件實際上就是一個 div,只不過在 InfinitView 內(nèi)部監(jiān)聽了該 div 的滾動事件。在即將觸底的時候去調(diào)用從父組件中傳過來的 onload 函數(shù)。

其 template 實現(xiàn)如下:

<div
  class="infinite-view"
  :style="classStyle"
  @scroll="onScroll($event.target)"
>
  <slot />
</div>
  • 可以通過 classStyle 在外部設(shè)置 InfinitView 組件的樣式。
  • 觸發(fā)滾動事件的時候,會執(zhí)行 onScroll 函數(shù)。onScroll 函數(shù)中屏蔽了調(diào)用 onload 函數(shù)的細節(jié)(觸底加載、節(jié)流加載)。
  • 使用 slot 將 InfinitView 的子組件當(dāng)成該 div 的子組件。

其 style 樣式如下:

.infinite-view {
  height: 100%;
  overflow-y: scroll;
}

InfinitView 組件的高度由其父元素決定,默認為其父元素高度的 100%,這就限制了其父元素的高度不能由 InfinitView 撐開決定。

其 script 如下:

import { ref, defineProps } from 'vue';

const props = defineProps({
  distance: {
    type: Number,
    default: 30,
  },
  onload: {
    type: Function,
    default: async () => {},
  },
  classStyle: {
    type: Object,
    default: () => ({}),
  },
});

const isloading = ref(false);

const onScroll = async (element) => {
  if (isloading.value) {
    return;
  }
  if (element.scrollHeight <= element.scrollTop + element.offsetHeight + props.distance) {
    try {
      isloading.value = true;
      await props.onload();
      isloading.value = false;
    } catch (error) {
      console.log(error);
      isloading.value = false;
    }
  }
}
  • 判斷觸底條件:scrollHeight <= scrollTop + clientHeight + distance

  • scrollHeight 代表整個滾動區(qū)域的高度。
  • scrollTop 是向上滾動的距離,即從內(nèi)容區(qū)頂部到整個滾動區(qū)域頂部的距離。
  • clientHeight 是內(nèi)容區(qū)的高度。
  • distance 代表觸底的距離,它是一個緩沖距離,即在內(nèi)容區(qū)底部距離整個滾動區(qū)域底部距離小于等于 distance 時,會觸發(fā) onload 函數(shù)。

當(dāng) scrollHeight === scrollTop + clientHeight 時,剛好滑動到底部。一般情況下,我們可以提前加載,設(shè)置一個緩沖距離 distance,當(dāng)即將滑動到底部,距底部不足 distance 的距離時,就可以觸發(fā)加載函數(shù)。

  • isloading 用來控制加載的狀態(tài),并實現(xiàn)節(jié)流加載。

加載函數(shù) onload 一般是異步函數(shù),用來請求列表數(shù)據(jù)。在執(zhí)行 onload 函數(shù)之前,將 isloading 設(shè)為 true,表示正在加載中;當(dāng) onload 函數(shù)執(zhí)行完之后,將 isloading 設(shè)為 false,表示加載狀態(tài)結(jié)束。

我們知道,scroll 事件是會頻繁觸發(fā)的,只要列表在滾動,onScroll 函數(shù)就會一直執(zhí)行。

這就有可能導(dǎo)致:當(dāng)滑動至距離底部不足 distance 距離時,滿足觸底條件,列表還在持續(xù)滾動,此時就會持續(xù)執(zhí)行 onload 函數(shù)發(fā)送請求,即使上一次請求還沒回來,瀏覽器也會持續(xù)請求同一頁列表數(shù)據(jù)。

所以需要實現(xiàn)節(jié)流加載,控制 onload 函數(shù)的執(zhí)行頻率。如果上一次請求還沒回來,則不執(zhí)行 onload 函數(shù)。也就是在觸底條件之前,如果上一次請求還在加載中,直接 return 掉。

const onScroll = async (element) => {
  // 如果上一次請求還沒回來,直接 return
  if (isloading.value) {
    return;
  }
  if (element.scrollHeight <= element.scrollTop + element.offsetHeight + props.distance) {
    try {
      // 在請求之前將 isloading 置為 true
      isloading.value = true;
      await props.onload();
      // 請求成功之后將 isloading 置為 false
      isloading.value = false;
    } catch (error) {
      console.log(error);
      // 請求失敗之后也將 isloading 置為 false
      isloading.value = false;
    }
  }
}

實現(xiàn)的時候有兩個細節(jié)需要提一下:scroll 事件不觸發(fā)、如何將 loading 狀態(tài)放在無限加載組件中進行管理。

scroll 事件

有時候經(jīng)常會遇到屏幕在滾動,但是一直沒有觸發(fā) scroll 事件。那是因為雖然屏幕滾動了,但是監(jiān)聽 scroll 事件的 div 并沒有滾動。

當(dāng)然我們可以省事地為 window 設(shè)置監(jiān)聽 scroll 的事件,不管是哪個元素觸發(fā)的 scroll 事件,最終都會冒泡到 window 上面,設(shè)置的 scroll 回調(diào)函數(shù)也總是會執(zhí)行。

// 在 InfinitView 中,組件掛載之后,為 window 設(shè)置監(jiān)聽 scroll 的事件
onMounted(() => {
  window.addEventListener('scroll', (e) => {
    onScroll(e.target);
  })
})

上面的代碼在 InfinitView 組件中,為 window 設(shè)置了監(jiān)聽 scroll 的事件。當(dāng)屏幕滾動時,就會執(zhí)行 onScroll 函數(shù)。這樣也是沒問題的,確實可以解決 scroll 事件不觸發(fā)的問題。

但是我們并沒有找到問題的根源,為什么在 InfinitView 組件中的 div 上面監(jiān)聽的 scroll 事件,卻不會觸發(fā)?

首先得知道什么情況下才會觸發(fā)滾動事件

  • 父元素高度比其所有子元素高度之和小。
  • 父元素的 overflow 屬性值為:auto | scroll。

只有滿足了以上兩個條件,才會觸發(fā)父元素的 scroll 事件。很多時候,某個 div 的 scroll 事件沒有觸發(fā),是因為我們沒有設(shè)置該 div 的高度,它的高度由子元素撐開,和子元素高度之和相等。

這樣即使屏幕在滾動,觸發(fā)的也不是它的 scroll 事件,而是更上層 div 的 scroll 事件。例如:如果某個 div 的高度由子元素撐開,并且其父元素高度確定,比它的高度小,則在滾動的時候,不會觸發(fā)該 div 的 scroll 事件,會觸發(fā)它的父元素的 scroll 事件。

或者我們忘記設(shè)置監(jiān)聽 scroll 事件的元素的 overflow 屬性了,默認情況下,overflow 的值為 visible。

$emit 發(fā)射事件和 props 回調(diào)函數(shù)的區(qū)別

我們知道在 Vue 中,子組件向父組件通信有兩種方式:通過 $emit 發(fā)射事件、通過調(diào)用父組件中傳過來的回調(diào)函數(shù)。

這兩種方式都可以由子組件向父組件通信,但是也有一些細微的區(qū)別:

  • 通過調(diào)用父組件中傳過來的回調(diào)函數(shù)可以拿到函數(shù)的返回值,而通過 $emit 發(fā)射事件不可以。
  • 通過調(diào)用父組件中傳過來的回調(diào)函數(shù)可以知道函數(shù)什么時候執(zhí)行完,而通過 $emit 發(fā)射事件不可以,它只能將回調(diào)函數(shù)通過 $emit 的參數(shù),傳給父組件,強迫父組件顯式調(diào)用,才能在函數(shù)執(zhí)行完之后做一些事情。

看起來,好像使用 props 回調(diào)函數(shù)比 $emit 發(fā)射事件要更好,那是不是 $emit 發(fā)射事件就沒有好處了呢?

也不是。從名字就可以看出,$emit 發(fā)射事件,是從子組件中發(fā)射一個事件給父組件,父組件在監(jiān)聽到子組件發(fā)射的事件之后,可以進行一系列的操作。它只是給父組件發(fā)射一個事件,傳遞一個信號給父組件,父組件接收到這個信號之后,接下來要怎么做還是由父組件決定。但是使用 props 回調(diào)函數(shù)的方式則不同,它是將父組件中的一個函數(shù)通過 props 傳給子組件,子組件拿到這個回調(diào)函數(shù)之后,要怎么執(zhí)行,完全取決于子組件。所以子組件可以知道回調(diào)函數(shù)什么時候執(zhí)行完,也可以拿到回調(diào)函數(shù)的返回值。

了解了這兩種通信方式的區(qū)別,就解決了如何將 loading 狀態(tài)放在無限加載組件中進行管理。

因為需要將 loading 狀態(tài)放在無限加載組件(子組件)中進行管理,所以無限加載組件(子組件)必須要知道請求什么時候回來,也就是 onload 異步函數(shù)什么時候執(zhí)行完。

這樣我們就可以用 props 回調(diào)函數(shù)的形式,將父組件中的異步請求函數(shù)傳給子組件,當(dāng)列表即將滾動到底部時,將 loading 狀態(tài)置為 true,然后發(fā)送請求,當(dāng)請求回來之后,異步函數(shù)執(zhí)行完,再將 loading 狀態(tài)置為 false。如果請求沒有回來,loading 狀態(tài)為 true,即使再次觸發(fā)了 scroll 事件,也直接返回,不再繼續(xù)發(fā)送請求。

const onScroll = async (element) => {
  // 如果上一次請求還沒回來,直接 return
  if (isloading.value) {
    return;
  }
  if (element.scrollHeight <= element.scrollTop + element.offsetHeight + props.distance) {
    try {
      // 在請求之前將 isloading 置為 true
      isloading.value = true;
      await props.onload();
      // 請求成功之后將 isloading 置為 false
      isloading.value = false;
    } catch (error) {
      console.log(error);
      // 請求失敗之后也將 isloading 置為 false
      isloading.value = false;
    }
  }
}

總結(jié)

到此這篇關(guān)于Vue簡易版無限加載組件實現(xiàn)原理的文章就介紹到這了,更多相關(guān)Vue無限加載組件實現(xiàn)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 關(guān)于element中表格和表單的封裝方式

    關(guān)于element中表格和表單的封裝方式

    這篇文章主要介紹了關(guān)于element中表格和表單的封裝方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • vue版數(shù)字翻牌器的封裝

    vue版數(shù)字翻牌器的封裝

    這篇文章主要為大家詳細介紹了vue版數(shù)字翻牌器的封裝,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • vue-loader中引入模板預(yù)處理器的實現(xiàn)

    vue-loader中引入模板預(yù)處理器的實現(xiàn)

    這篇文章主要介紹了vue-loader中引入模板預(yù)處理器的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • Vue中 Runtime + Compiler 和 Runtime-only 兩種模式含義和區(qū)別詳解

    Vue中 Runtime + Compiler 和 Runtime-o

    這篇文章主要介紹了Vue中 Runtime + Compiler 和 Runtime-only 兩種模式含義和區(qū)別,結(jié)合實例形式詳細分析了Vue中 Runtime + Compiler 和 Runtime-only 兩種模式基本功能、原理、區(qū)別與相關(guān)注意事項,需要的朋友可以參考下
    2023-06-06
  • 在線使用iconfont字體圖標(biāo)的簡單實現(xiàn)

    在線使用iconfont字體圖標(biāo)的簡單實現(xiàn)

    這篇文章主要介紹了在線使用iconfont字體圖標(biāo)的簡單實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • vue使用highcharts自定義圖例點擊事件

    vue使用highcharts自定義圖例點擊事件

    這篇文章主要為大家詳細介紹了vue使用highcharts自定義圖例點擊事件,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • vue+element自定義查詢組件

    vue+element自定義查詢組件

    這篇文章主要為大家詳細介紹了vue+element自定義查詢組件,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-09-09
  • 使用Vue3和Echarts?5繪制帶有立體感流線中國地圖(推薦收藏!)

    使用Vue3和Echarts?5繪制帶有立體感流線中國地圖(推薦收藏!)

    最近接到一個需求是做一個中國地圖,下面這篇文章主要給大家介紹了關(guān)于如何使用Vue3和Echarts?5繪制帶有立體感流線中國地圖的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下
    2022-04-04
  • 基于vue實現(xiàn)多功能樹形結(jié)構(gòu)組件的示例代碼

    基于vue實現(xiàn)多功能樹形結(jié)構(gòu)組件的示例代碼

    一個優(yōu)雅展示樹形結(jié)構(gòu)數(shù)據(jù)的 Vue 組件,遞歸渲染每個節(jié)點及其子節(jié)點,支持自定義顏色、文本和布局,通過獨特的樣式巧妙處理不同層級,為用戶打造豐富的視覺盛宴,文中通過代碼給大家介紹的非常詳細,感興趣的同學(xué)可以自己動手嘗試一下
    2024-02-02
  • Vue中watch與watchEffect的區(qū)別詳細解讀

    Vue中watch與watchEffect的區(qū)別詳細解讀

    這篇文章主要介紹了Vue中watch與watchEffect的區(qū)別詳細解讀,watch函數(shù)與watchEffect函數(shù)都是監(jiān)聽器,在寫法和用法上有一定區(qū)別,是同一功能的兩種不同形態(tài),底層都是一樣的,需要的朋友可以參考下
    2023-11-11

最新評論