Vue 列表过滤:语法与注意事项详解
在 Vue 中,列表过滤是常见的数据处理需求,正确实现能显著提升应用性能和用户体验。下面我将全面介绍 Vue 中列表过滤的语法和注意事项。
核心语法与实现方式
1. 使用计算属性(推荐)
computed: {
filteredItems() {
const searchTerm = this.searchText.toLowerCase();
return this.items.filter(item =>
item.name.toLowerCase().includes(searchTerm) ||
item.description.toLowerCase().includes(searchTerm)
);
}
}
模板中使用:
<div v-for="item in filteredItems" :key="item.id">
{{ item.name }}
</div>
2. 使用方法(不推荐用于渲染)
methods: {
filterItems(items) {
return items.filter(item =>
item.price > this.minPrice && item.price < this.maxPrice
);
}
}
模板中使用:
<div v-for="item in filterItems(items)" :key="item.id">
<!-- 不推荐:每次渲染都会重新计算 -->
</div>
3. 使用 Vue 过滤器(简单场景)
filters: {
priceFilter(items, minPrice) {
return items.filter(item => item.price >= minPrice);
}
}
模板中使用:
<div v-for="item in items | priceFilter(100)" :key="item.id">
{{ item.name }} - {{ item.price }}
</div>
关键注意事项
1. 性能优化
- 优先使用计算属性:自动缓存结果,避免重复计算
- 避免在模板中直接过滤:每次渲染都会重新执行
- 复杂过滤使用 Web Workers:防止阻塞主线程
// 复杂计算的 Web Worker 示例
const worker = new Worker('filter-worker.js');
worker.postMessage({ items: this.items, filter: this.filter });
worker.onmessage = (e) => {
this.filteredItems = e.data;
};
2. 空状态处理
<template v-if="filteredItems.length">
<div v-for="item in filteredItems" :key="item.id">
<!-- 内容 -->
</div>
</template>
<template v-else>
<div class="empty-state">
<p>未找到匹配结果</p>
<button @click="resetFilters">重置筛选</button>
</div>
</template>
3. 多条件过滤
computed: {
filteredItems() {
return this.items.filter(item => {
const matchesSearch = this.searchText
? item.name.includes(this.searchText)
: true;
const matchesCategory = this.selectedCategory
? item.category === this.selectedCategory
: true;
const inPriceRange = item.price >= this.minPrice &&
item.price <= this.maxPrice;
return matchesSearch && matchesCategory && inPriceRange;
});
}
}
4. 大型列表优化
- 虚拟滚动:只渲染可视区域元素
<VirtualScroller
:items="filteredItems"
:item-height="50"
:key="filteredItems.length"
>
<template v-slot:default="{ item }">
<div class="item">{{ item.name }}</div>
</template>
</VirtualScroller>
- 分页处理:结合过滤与分页
computed: {
paginatedItems() {
const start = (this.currentPage - 1) * this.pageSize;
return this.filteredItems.slice(start, start + this.pageSize);
},
totalPages() {
return Math.ceil(this.filteredItems.length / this.pageSize);
}
}
5. 输入处理与防抖
data() {
return {
searchText: '',
debouncedSearch: ''
};
},
watch: {
searchText(newVal) {
clearTimeout(this.debounceTimeout);
this.debounceTimeout = setTimeout(() => {
this.debouncedSearch = newVal;
}, 300); // 300ms 防抖
}
},
computed: {
filteredItems() {
// 使用 debouncedSearch 而非 searchText
}
}
6. 过滤与排序结合
computed: {
processedItems() {
const filtered = this.items.filter(/* 过滤逻辑 */);
return filtered.sort((a, b) => {
if (this.sortBy === 'price') {
return this.sortAsc ? a.price - b.price : b.price - a.price;
}
// 其他排序逻辑...
});
}
}
最佳实践总结
场景 | 推荐方案 | 不推荐方案 |
---|---|---|
小型列表 | 计算属性 | 模板内过滤 |
大型列表 | 虚拟滚动 + 分页 | 一次性渲染 |
复杂计算 | Web Workers | 主线程计算 |
用户输入 | 防抖处理 | 实时过滤 |
多条件 | 组合过滤 | 嵌套条件 |
空状态 | 友好提示 | 空白显示 |
完整示例:电商商品过滤
<template>
<div class="product-filter">
<!-- 搜索框 -->
<input
v-model="searchText"
placeholder="搜索商品..."
class="search-input"
>
<!-- 价格范围 -->
<div class="price-range">
<label>价格范围:</label>
<input type="number" v-model.number="minPrice" placeholder="最低">
<span>-</span>
<input type="number" v-model.number="maxPrice" placeholder="最高">
</div>
<!-- 类别筛选 -->
<div class="category-filter">
<label>类别:</label>
<select v-model="selectedCategory">
<option value="">全部</option>
<option v-for="cat in categories" :key="cat" :value="cat">
{{ cat }}
</option>
</select>
</div>
<!-- 排序 -->
<div class="sort-options">
<label>排序:</label>
<select v-model="sortBy">
<option value="price">价格</option>
<option value="name">名称</option>
<option value="rating">评分</option>
</select>
<button @click="sortAsc = !sortAsc">
{{ sortAsc ? '升序 ↑' : '降序 ↓' }}
</button>
</div>
<!-- 结果展示 -->
<div v-if="filteredItems.length > 0" class="results">
<div class="result-count">
找到 {{ filteredItems.length }} 个商品
<span v-if="paginatedItems.length < filteredItems.length">
(显示 {{ paginatedItems.length }} 个)
</span>
</div>
<!-- 虚拟滚动容器 -->
<VirtualScroller
:items="paginatedItems"
:item-height="100"
class="product-list"
>
<template v-slot:default="{ item }">
<ProductCard :product="item" />
</template>
</VirtualScroller>
<!-- 分页控件 -->
<Pagination
:current-page="currentPage"
:total-pages="totalPages"
@page-change="currentPage = $event"
/>
</div>
<!-- 空状态 -->
<div v-else class="empty-state">
<img src="@/assets/no-results.svg" alt="无结果">
<h3>未找到匹配的商品</h3>
<p>请尝试修改筛选条件</p>
<button @click="resetFilters">重置筛选</button>
</div>
</div>
</template>
<script>
import { debounce } from 'lodash-es';
export default {
data() {
return {
searchText: '',
minPrice: 0,
maxPrice: 10000,
selectedCategory: '',
sortBy: 'price',
sortAsc: true,
currentPage: 1,
pageSize: 20,
items: [], // 从API获取的实际商品数据
debouncedSearch: ''
};
},
computed: {
// 获取唯一类别列表
categories() {
return [...new Set(this.items.map(item => item.category))];
},
// 过滤后的商品
filteredItems() {
const searchTerm = this.debouncedSearch.toLowerCase();
return this.items.filter(item => {
const matchesSearch = searchTerm
? item.name.toLowerCase().includes(searchTerm) ||
item.description.toLowerCase().includes(searchTerm)
: true;
const matchesCategory = this.selectedCategory
? item.category === this.selectedCategory
: true;
const inPriceRange = item.price >= this.minPrice &&
item.price <= this.maxPrice;
return matchesSearch && matchesCategory && inPriceRange;
}).sort((a, b) => {
// 排序逻辑
if (this.sortBy === 'price') {
return this.sortAsc ? a.price - b.price : b.price - a.price;
}
if (this.sortBy === 'name') {
return this.sortAsc
? a.name.localeCompare(b.name)
: b.name.localeCompare(a.name);
}
if (this.sortBy === 'rating') {
return this.sortAsc ? a.rating - b.rating : b.rating - a.rating;
}
return 0;
});
},
// 分页后的商品
paginatedItems() {
const start = (this.currentPage - 1) * this.pageSize;
return this.filteredItems.slice(start, start + this.pageSize);
},
// 总页数
totalPages() {
return Math.ceil(this.filteredItems.length / this.pageSize);
}
},
watch: {
// 搜索防抖
searchText: debounce(function(newVal) {
this.debouncedSearch = newVal;
this.currentPage = 1; // 重置到第一页
}, 300),
// 其他筛选条件变化时重置页码
minPrice() { this.currentPage = 1; },
maxPrice() { this.currentPage = 1; },
selectedCategory() { this.currentPage = 1; },
sortBy() { this.currentPage = 1; },
sortAsc() { this.currentPage = 1; }
},
methods: {
resetFilters() {
this.searchText = '';
this.minPrice = 0;
this.maxPrice = 10000;
this.selectedCategory = '';
this.sortBy = 'price';
this.sortAsc = true;
}
},
mounted() {
// 从API获取数据
this.fetchProducts();
}
};
</script>
性能监控与调试
使用 Vue 性能工具监控过滤性能:
// 在过滤计算属性中添加性能标记
filteredItems() {
const start = performance.now();
// ...过滤逻辑
const duration = performance.now() - start;
if (duration > 50) {
console.warn(`过滤耗时 ${duration.toFixed(2)}ms,建议优化`);
}
return result;
}
总结:高效过滤的黄金法则
- 计算属性优先:利用 Vue 的响应式系统和缓存机制
- 大型数据分治:分页 + 虚拟滚动解决性能瓶颈
- 用户交互优化:防抖处理 + 即时反馈
- 空状态体验:提供清晰的指引和重置选项
- 组合过滤排序:逻辑分离,便于维护
- 性能监控:主动检测潜在性能问题
正确实现列表过滤不仅能提升应用性能,还能显著改善用户体验。根据数据量和复杂度选择合适的过滤策略,是 Vue 开发中的关键技能之一。