Vuetify v3中v-combobox加载10万条数据卡顿,求懒加载方案
Vuetify v3 v-combobox 十万条数据卡顿解决(懒加载/虚拟滚动方案)
直接把十万条数据传给v-combobox的items属性,会导致组件一次性渲染上万条DOM节点,直接卡死浏览器。下面给出几种可行的解决思路:
方案一:用虚拟滚动减少DOM渲染量
Vuetify的v-virtual-scroll组件可以只渲染当前视口内的列表项,不管数据量多大,DOM元素数量都维持在几十条,彻底解决卡顿。通过自定义v-combobox的menu插槽来实现:
<v-col v-for="property in filterableProperties" :key="property.code"> <v-combobox v-model="filter[property.code]" :label="property.label" variant="outlined" hide-details bg-color="white" clearable :menu-props="{ maxHeight: 400 }" > <!-- 自定义下拉菜单内容,替换默认列表 --> <template v-slot:menu="{ isOpen }"> <v-virtual-scroll v-if="isOpen" :items="values[property.code]" :item-height="48" <!-- 匹配v-list-item默认高度 --> height="400" > <template v-slot:item="{ item }"> <v-list-item :title="item" @click="selectItem(property.code, item)" /> </template> </v-virtual-scroll> </template> </v-combobox> </v-col>
对应的脚本逻辑(手动处理选中赋值):
import { ref } from 'vue'; const filter = ref({}); function selectItem(code, item) { filter.value[code] = item; }
方案二:搜索触发懒加载(按需过滤数据)
如果不需要展示全部数据,只需要根据用户输入的关键词返回匹配结果,可以监听v-combobox的search事件,每次仅过滤/加载少量数据:
<v-col v-for="property in filterableProperties" :key="property.code"> <v-combobox v-model="filter[property.code]" :label="property.label" :items="filteredValues[property.code]" variant="outlined" hide-details bg-color="white" clearable :loading="loading[property.code]" @search="handleSearch(property.code, $event)" /> </v-col>
脚本逻辑:
import { ref } from 'vue'; const filter = ref({}); const filteredValues = ref({}); const loading = ref({}); const pageSize = 20; // 每次加载20条数据 // 初始化每个属性的过滤列表状态 filterableProperties.forEach(prop => { filteredValues.value[prop.code] = []; loading.value[prop.code] = false; }); function handleSearch(code, searchQuery) { loading.value[code] = true; // 这里可以替换为后端接口请求,传入searchQuery和页码 setTimeout(() => { const allItems = values[code]; // 根据搜索词过滤数据,仅返回前pageSize条 const matchedItems = allItems.filter(item => item.toLowerCase().includes(searchQuery.toLowerCase()) ).slice(0, pageSize); filteredValues.value[code] = matchedItems; loading.value[code] = false; }, 100); }
方案三:虚拟滚动 + 滚动加载更多(过滤后数据仍较多的场景)
如果搜索后的匹配结果仍有几千条,可以结合虚拟滚动和滚动触底加载,进一步优化:
<v-col v-for="property in filterableProperties" :key="property.code"> <v-combobox v-model="filter[property.code]" :label="property.label" variant="outlined" hide-details bg-color="white" clearable :menu-props="{ maxHeight: 400 }" :loading="loading[property.code]" @search="handleSearch(property.code, $event)" > <template v-slot:menu="{ isOpen }"> <v-virtual-scroll v-if="isOpen" :items="filteredValues[property.code]" :item-height="48" height="400" @scroll="handleScroll(property.code, $event)" > <template v-slot:item="{ item }"> <v-list-item :title="item" @click="selectItem(property.code, item)" /> </template> <!-- 加载状态提示 --> <template v-slot:append> <v-progress-linear v-if="loading[property.code]" indeterminate class="ma-2" /> </template> </v-virtual-scroll> </template> </v-combobox> </v-col>
补充脚本逻辑:
import { ref } from 'vue'; const filter = ref({}); const filteredValues = ref({}); const loading = ref({}); const currentPage = ref({}); const pageSize = 20; filterableProperties.forEach(prop => { filteredValues.value[prop.code] = []; loading.value[prop.code] = false; currentPage.value[prop.code] = 1; }); function selectItem(code, item) { filter.value[code] = item; } function handleSearch(code, searchQuery) { currentPage.value[code] = 1; loading.value[code] = true; setTimeout(() => { const allItems = values[code]; const matchedItems = allItems.filter(item => item.toLowerCase().includes(searchQuery.toLowerCase()) ); filteredValues.value[code] = matchedItems.slice(0, pageSize); loading.value[code] = false; }, 100); } function handleScroll(code, event) { const container = event.target; // 判断是否滚动到接近底部(预留10px的误差) if (container.scrollTop + container.clientHeight >= container.scrollHeight - 10) { if (loading.value[code]) return; loading.value[code] = true; currentPage.value[code]++; setTimeout(() => { const allItems = values[code]; const matchedItems = allItems.filter(item => item.toLowerCase().includes(filter.value[code]?.toLowerCase() || '') ); // 计算下一页数据的起始和结束索引 const start = (currentPage.value[code] - 1) * pageSize; const end = currentPage.value[code] * pageSize; const nextPageItems = matchedItems.slice(start, end); filteredValues.value[code] = [...filteredValues.value[code], ...nextPageItems]; loading.value[code] = false; }, 100); } }
内容的提问来源于stack exchange,提问作者Mohammed
相关产品推荐
相关产品推荐

