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