Vue 列表过滤:语法与注意事项详解

发布于:2025-06-24 ⋅ 阅读:(14) ⋅ 点赞:(0)

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;
}

总结:高效过滤的黄金法则

  1. 计算属性优先:利用 Vue 的响应式系统和缓存机制
  2. 大型数据分治:分页 + 虚拟滚动解决性能瓶颈
  3. 用户交互优化:防抖处理 + 即时反馈
  4. 空状态体验:提供清晰的指引和重置选项
  5. 组合过滤排序:逻辑分离,便于维护
  6. 性能监控:主动检测潜在性能问题

正确实现列表过滤不仅能提升应用性能,还能显著改善用户体验。根据数据量和复杂度选择合适的过滤策略,是 Vue 开发中的关键技能之一。


网站公告

今日签到

点亮在社区的每一天
去签到