當下拉列表資料過大時,該如何應對?
在日常開發中,除了現成外掛的使用外,還有很多問題是隻能自己動手的。先丟擲問題,當一個下拉列表的資料達到幾千條甚至上萬,這個時候瀏覽器已經會出現嚴重卡頓了。看看下面的例子

如圖所示,資料量達到 2W 條 簡單測試資料 (頁面沒有其他東西),點選載入下拉列表花了大概 5s 時間。出現這種情況心裡真的是很複雜,這不是在玩我嗎?

解決思路
這個問題其實和表格資料是同一個效能問題,表格的解決方式是通過分頁器來減少頁面承載的資料量。那麼下拉列表該如何解決呢?通常我們都是一次性載入下拉的所有資料的,針對目前的難題,思路也是一樣,採用分頁來解決頁面的效能問題。問題又來了,分頁器是可以點選的,那下拉列表又不可以點選,那就只有在監聽滾動事件裡實現這件大事了。先來大綱:
- 監聽滾動
- 向下滾動時往後載入資料
- 向上滾動時往前載入資料
- 資料有進有出
好戲開始
監聽滾動
<el-select class="remoteSelect" v-scroll v-model="value"> <el-option :value="item.id" v-for="item in list" :key="item.id">{{item.name}}</el-option> </el-select> 複製程式碼
這裡是基於vue與element-ui中 el-select 實現的監聽滾動。這裡是採用自定義指令的方式監聽滾動
// directives目錄下index.js檔案 import Vue from 'vue' export default () => { Vue.directive('scroll', { bind (el, binding) { // 獲取滾動頁面DOM let SCROLL_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap') SCROLL_DOM.addEventListener('scroll', function () { console.log('scrll') }) } }) } 複製程式碼
在main.js中通過全域性方法Vue.use()註冊使用
import Directives from './directives' Vue.use(Directives) 複製程式碼
這時滾動頁面就可以看到控制的列印日誌,代表監聽已生效,接下來擼起袖子開幹

向下滾動時往後載入資料
首先要先判斷出是向上滾動,還是向下滾動
- 記錄上一次的滾動位置
- 當前位置與上一次的滾動位置作比較
通過一個公共變數來記錄全域性位置,通過 scrollTop
方法獲取當前的滾動位置,並記錄在公共變數 scrollPosition
裡
bind (el, binding) { // 獲取滾動頁面DOM let SCROLL_DOM = el.querySelector('.el-select-dropdown .el-select-dropdown__wrap') let scrollPosition = 0 SCROLL_DOM.addEventListener('scroll', function () { // 當前的滾動位置 減去上一次的滾動位置 // 如果為true則代表向上滾動,false代表向下滾動 let flagToDirection = this.scrollTop - scrollPosition > 0 // 記錄當前的滾動位置 scrollPosition = this.scrollTop console.log(flagToDirection ? '滾動方向:下' : '滾動方向:上') }) } 複製程式碼

目前已知曉滾動的方向,接下來便根據滾動方向做相應的處理。將滾動行為告訴元件
...省略 // 記錄當前的滾動位置 scrollPosition = this.scrollTop // 將滾動行為告訴元件 binding.value(flagToDirection) 複製程式碼
事件接受在 v-scroll
指令中接受事件 v-scroll="handleScroll"
,在該方法 handleScroll
處理滾動行為。 接下來只需要在該事件中針對為向下的滾動發起請求資料即可
/********************************* ** Fn: handleScroll ** Intro: 處理滾動行為 ** @params: param 為true代表向下滾動 ** @params: param 為false代表向上滾動 *********************************/ handleScroll (param) { if (param) { // 請求下一頁的資料 this.list.push(...this.ajaxData(++this.pageIndex)) } }, 複製程式碼

到這裡滾動載入已經實現。只是載入太頻繁了,如果快速滾動則會同時發出多個請求後臺資料,在密集一些遊覽器中ajax就要開發併發排隊了,可見並不理想。那如何控制呢?那換種方式觸發 handleScroll
事件,在滾動位置距離滾動頁面底部一定高度時在觸發,例如距頁面底部只有 100px
時觸發 handleScroll
事件
scrollHeight
// 記錄當前的滾動位置 scrollPosition = this.scrollTop const LIMIT_BOTTOM = 100 // 記錄滾動位置距離底部的位置 let scrollBottom = this.scrollHeight - (this.scrollTop + this.clientHeight) < LIMIT_BOTTOM // 如果已達到指定位置則觸發 if (scrollBottom) { // 將滾動行為告訴元件 binding.value(flagToDirection) } 複製程式碼

通過資料長度的變化可以知道觸發事件已經明顯和諧了很多,這種效果很手機懶載入的方式一樣,資料會被不斷的疊加。
小提示:會存在一個bug,即ajax是非同步的,如果這個ajax請求花了 1s 才返回資料,而此時還在繼續往下滾,那就會觸發多個請求事件。如何避免這種情況呢? 答案是增加一個標誌位,在請求前將該標誌位設定為false,請求結束後設置為true。每次請求時先判斷該標誌位。如果為false則阻止該事件。
中場
再來看看我們的大綱
- 監聽滾動
- 向下滾動時往後載入資料
- 向上滾動時往前載入資料
- 資料有進有出
到這裡我們只完成①和②兩個步驟。如果已經滿足了你的需求,那你可以結束閱讀了。如果對你有那麼一點點幫助,先點個贊在離開。
前面說的都還只是基礎操作,還沒開始劃重點呢。說好的無效能壓力呢?

先下班回家吃飯吧。週末繼續寫完