國內(nèi)互動網(wǎng)站建設(shè)江門網(wǎng)站定制多少錢
遇到一個性能相關(guān)的問題,使用?Element Plus 的?<ElSelect> 組件在數(shù)據(jù)量很大時,加載速度變慢。
下面簡單分析下原因,并提供了一些解決方法。
1.?問題分析
1、大量 DOM 節(jié)點渲染
問題:當(dāng)數(shù)據(jù)量非常大時,每一個選項都會生成一個 DOM 節(jié)點。在 HTML 中,每一個?<option>?元素都需要單獨渲染,導(dǎo)致頁面需要處理大量 DOM 元素的加載和渲染,影響頁面性能。
影響:瀏覽器在渲染和操作大量 DOM 時效率會降低,導(dǎo)致組件初始化和操作(如滾動、過濾)變慢。
2、Vue 響應(yīng)式系統(tǒng)的性能瓶頸
問題:Vue 的響應(yīng)式系統(tǒng)會追蹤每個數(shù)據(jù)項的狀態(tài)變化。當(dāng) <ElSelect>?中的數(shù)據(jù)量過大時,Vue 的響應(yīng)式系統(tǒng)需要為每一個 Option 建立響應(yīng)式追蹤,增加內(nèi)存和計算的開銷,尤其是在更新數(shù)據(jù)、滾動或篩選時,這種情況會更加明顯。
影響:響應(yīng)式追蹤在數(shù)據(jù)項非常多的情況下可能導(dǎo)致瀏覽器出現(xiàn)卡頓,甚至出現(xiàn)頁面響應(yīng)不及時的情況。
3、事件監(jiān)聽和計算
問題:當(dāng)?<ElSelect>?中的數(shù)據(jù)項很多時,每次選擇、過濾或輸入,都會觸發(fā)事件監(jiān)聽器和計算操作。如果數(shù)據(jù)項非常多,這些操作會變得頻繁且耗時,增加組件的負擔(dān)。
影響:頁面響應(yīng)速度降低,用戶在操作組件時會感覺到明顯的卡頓。
4、過多的無意義的渲染
問題:在默認實現(xiàn)中,<ElSelect>?會一次性渲染所有數(shù)據(jù)項,不管用戶是否在當(dāng)前視口中看到這些數(shù)據(jù)。即便用戶只滾動一小部分,整個組件仍然會處理所有數(shù)據(jù)項,導(dǎo)致加載速度慢。
影響:瀏覽器資源被過度消耗,渲染效率降低,頁面加載時間延長。
2.?解決方案
1、使用虛擬滾動
方法:借助虛擬滾動技術(shù)(如 Element Plus 的?ElVirtualizedSelect 組件),只渲染當(dāng)前視口中可見的部分數(shù)據(jù)。虛擬滾動技術(shù)通過動態(tài)加載和卸載數(shù)據(jù)項來減少頁面上的 DOM 節(jié)點數(shù)量。
這種方法能顯著減少 DOM 渲染的節(jié)點數(shù)量和內(nèi)存占用,提升渲染速度和用戶體驗。
🌰
<template><!-- 使用虛擬滾動的選擇框 --><el-select-v2v-model="selectedValue":options="options"placeholder="請選擇"style="width: 200px"/>
</template><script>
import { ref } from 'vue';export default {setup() {// 創(chuàng)建 10,000 條模擬數(shù)據(jù)const options = ref(Array.from({ length: 10000 }, (_, index) => ({value: index,label: `選項 ${index + 1}`})));const selectedValue = ref(null);return {options,selectedValue};}
};
</script>
效果:顯著減少 DOM 中節(jié)點數(shù)量,提升了渲染性能。對于大數(shù)據(jù)場景,只有可見選項會被加載和渲染,大大降低了內(nèi)存和渲染開銷。
文檔:https://element-plus.org/zh-CN/component/select-v2.html
對比:普通的 <ElSelect> 組件,在最開始渲染全部的 Option 元素。
<template><!-- 使用普通的選擇框 --><el-select v-model="value" placeholder="Select" style="width: 240px"><el-optionv-for="item in options":key="item.value":label="item.label":value="item.value"/></el-select>
</template><script>
import { ref } from 'vue'
export default {setup() {// 創(chuàng)建 100 條模擬數(shù)據(jù),防止頁面卡住const options = ref(Array.from({ length: 100 }, (_, index) => ({value: index,label: `選項 ${index + 1}`})))const selectedValue = ref(null)return {options,selectedValue}}
}
</script>
而?<el-select-v2> 組件只渲染展示的一部分,顯而易見的提升了渲染性能。
2、分頁加載或懶加載
方法:將數(shù)據(jù)進行分頁或分批次加載。比如,可以設(shè)置一個加載閾值,先加載一部分數(shù)據(jù)項,用戶向下滾動到一定程度再加載下一部分數(shù)據(jù)項。
避免一次性加載大量數(shù)據(jù),減少頁面初始化時的加載壓力。
🌰
<template><el-selectv-model="selectedValue"placeholder="請選擇"filterable@visible-change="handleVisibleChange"><el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" /></el-select>
</template>
<script>
import { ref, nextTick } from 'vue'
export default {setup() {const options = ref([])const page = ref(1)const selectedValue = ref(null)// 模擬 API 獲取分頁數(shù)據(jù)const loadOptions = async () => {const newOptions = await fetchOptions(page.value)options.value.push(...newOptions)page.value++}// 處理滾動事件const handleScroll = (event) => {const { scrollTop, clientHeight, scrollHeight } = event.targetif (scrollTop + clientHeight >= scrollHeight - 10) {loadOptions()}}// 監(jiān)聽下拉框的可見性變化const handleVisibleChange = async () => {await nextTick()const dropdown = document.querySelector('.el-select-dropdown .el-scrollbar__wrap')if (dropdown) {dropdown.addEventListener('scroll', handleScroll)}}// 初始加載loadOptions()return {options,selectedValue,handleVisibleChange}}
}
// 模擬 API 調(diào)用,獲取分頁數(shù)據(jù)
async function fetchOptions(page) {return Array.from({ length: 10 }, (_, index) => ({value: (page - 1) * 10 + index,label: `選項 ${(page - 1) * 10 + index + 1}`}))
}
</script>
效果:初次加載僅渲染一部分數(shù)據(jù),滾動到列表底部時加載更多。通過分頁,可以避免一次性加載全部數(shù)據(jù),減少頁面初始化的負擔(dān)。
展示:
以此類推,直到數(shù)據(jù)加載完成后結(jié)束。
3、減少不必要的響應(yīng)式追蹤
方法:將不需要響應(yīng)式的數(shù)據(jù)項轉(zhuǎn)換為非響應(yīng)式對象或深度克隆數(shù)據(jù)。Vue 3 提供了?shallowRef 和?shallowReactive,可用來減少不必要的響應(yīng)式開銷。
效果:降低 Vue 響應(yīng)式系統(tǒng)的性能開銷,提升加載和操作的流暢度。
🌰
<template><el-select v-model="selectedValue" placeholder="請選擇"><el-optionv-for="item in nonReactiveOptions":key="item.value":label="item.label":value="item.value"/></el-select>
</template><script>
import { shallowRef, ref } from 'vue';
export default {setup() {// 使用 shallowRef 包裝不需要響應(yīng)式的數(shù)據(jù)const nonReactiveOptions = shallowRef(Array.from({ length: 1000 }, (_, index) => ({value: index,label: `選項 ${index + 1}`})));const selectedValue = ref(null);return {nonReactiveOptions,selectedValue};}
};
</script>
使用 shallowRef 后,Vue 不會深度監(jiān)聽 nonReactiveOptions 的變化,僅在整個對象被替換時觸發(fā)重新渲染,這樣減少 Vue 對數(shù)據(jù)的追蹤和性能開銷。
展示:一次性加載完,但不會跟蹤內(nèi)部變化。
4、減少過度的事件監(jiān)聽
方法:對用戶輸入和操作添加防抖或節(jié)流處理,避免頻繁地觸發(fā)數(shù)據(jù)項的更新和過濾。比如使用?lodash.debounce?限制輸入框觸發(fā)的過濾頻率。
🌰
<template><el-select v-model="selectedValue" filterable @input="onInput" placeholder="請選擇"><el-optionv-for="item in filteredOptions":key="item.value":label="item.label":value="item.value"/></el-select>
</template><script>
import { ref, computed } from 'vue';
import debounce from 'lodash/debounce';export default {setup() {const options = ref(Array.from({ length: 1000 }, (_, index) => ({value: index,label: `選項 ${index + 1}`})));const searchQuery = ref('');const selectedValue = ref(null);// 使用防抖處理輸入事件const onInput = debounce((value) => {searchQuery.value = value;}, 300);const filteredOptions = computed(() =>options.value.filter((item) =>item.label.includes(searchQuery.value)));return {filteredOptions,selectedValue,onInput};}
};
</script>
效果:只有在輸入停止 300 毫秒后,才會觸發(fā)過濾邏輯,從而避免了輸入框內(nèi)容頻繁更新導(dǎo)致的高計算開銷。這個方法適用于需要實時過濾的場景。
展示:
總結(jié):
當(dāng)?Element Plus 的?<ElSelect>?組件加載大量數(shù)據(jù)時,主要是 DOM 渲染、Vue 響應(yīng)式追蹤和事件計算等導(dǎo)致性能下降。通過使用虛擬滾動、分頁加載、減少響應(yīng)式追蹤以及事件防抖等方法,可以顯著優(yōu)化加載性能,使組件在大數(shù)據(jù)量下也能流暢運行。