watch、computed与生命周期在筛选面板中的应用

182 阅读5分钟

Vue3核心特性实战:watch、computed与生命周期在筛选面板中的应用

作为一名前端开发者,我将在本文通过构建一个完整的筛选面板组件,深入讲解Vue3中watchcomputed和生命周期钩子的使用技巧。我们将从零开始构建一个支持多条件筛选的数据表格,涵盖从基础语法到高级应用场景的完整流程。

目录

  1. 环境准备
  2. 筛选面板需求分析
  3. 响应式数据基础
  4. computed计算属性实战
  5. watch侦听器进阶应用](#watch侦听器进阶应用)
  6. 生命周期钩子集成
  7. 完整代码示例
  8. 最佳实践总结

环境准备

我们使用Vue3+Vite快速搭建开发环境:

npm init vite@latest vue3-filter-panel --template vue
cd vue3-filter-panel
npm install

修改main.js为组合式API风格:

import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

筛选面板需求分析

我们要构建一个包含以下功能的筛选面板:

  • 多选分类筛选(Category)
  • 价格区间滑块(Price Range)
  • 评分筛选(Rating)
  • 实时搜索框(Search)
  • 数据表格展示筛选结果

响应式数据基础

首先创建FilterPanel.vue组件,使用组合式API定义基本状态:

<script setup>
import { ref, reactive, computed } from 'vue'

// 原始数据集
const products = ref([
  { id: 1, name: 'Product A', category: 'Electronics', price: 99, rating: 4.5 },
  // ...更多产品数据
])

// 筛选条件状态对象
const filterState = reactive({
  categories: [],
  priceRange: [0, 1000],
  minRating: 0,
  searchQuery: ''
})
</script>

为什么使用reactive而不是ref

当需要管理多个相关联的状态时,reactive能更方便地保持对象的响应式,特别是在后续需要整体重置或批量更新时。

computed计算属性实战

创建filteredProducts计算属性进行数据过滤:

// 计算筛选后的产品列表
const filteredProducts = computed(() => {
  return products.value.filter(product => {
    // 分类筛选
    if (filterState.categories.length && !filterState.categories.includes(product.category)) {
      return false
    }
    
    // 价格区间筛选
    if (product.price < filterState.priceRange[0] || product.price > filterState.priceRange[1]) {
      return false
    }
    
    // 评分筛选
    if (filterState.minRating > 0 && product.rating < filterState.minRating) {
      return false
    }
    
    // 搜索关键字匹配
    if (filterState.searchQuery && !product.name.toLowerCase().includes(filterState.searchQuery.toLowerCase())) {
      return false
    }
    
    return true
  })
})

关键优化点

  1. 将复杂过滤逻辑封装在计算属性中
  2. 自动依赖追踪,任何筛选条件变化都会触发重新计算
  3. 保持原始数据的不可变性

watch侦听器进阶应用

添加watch监听筛选条件变化:

// 监听筛选条件变化
watch(
  () => filterState,
  (newVal, oldVal) => {
    console.log('筛选条件已更新:', newVal)
    // 可在这里添加历史记录存储逻辑
  },
  { deep: true } // 深度监听嵌套对象变化
)

高级配置选项

// 立即执行一次的watch
watch(
  () => filterState.searchQuery,
  (query) => {
    // 防抖处理搜索请求
    clearTimeout(this.searchTimeout)
    this.searchTimeout = setTimeout(() => {
      console.log('搜索查询:', query)
    }, 300)
  },
  { immediate: true }
)

生命周期钩子集成

在组件挂载时加载初始数据:

import { onMounted } from 'vue'

onMounted(() => {
  // 模拟API请求
  setTimeout(() => {
    products.value = [...products.value, /* 新数据 */]
  }, 1000)
})

在卸载前清除定时器:

import { onBeforeUnmount } from 'vue'

let searchTimeout = null

onBeforeUnmount(() => {
  if (searchTimeout) clearTimeout(searchTimeout)
})

完整代码示例

<template>
  <div class="filter-panel">
    <!-- 分类筛选 -->
    <div>
      <h3>Categories</h3>
      <div v-for="cat in categories" :key="cat">
        <input type="checkbox" :id="'cat-'+cat" :value="cat" v-model="filterState.categories"/>
        <label :for="'cat-'+cat">{{ cat }}</label>
      </div>
    </div>

    <!-- 价格区间 -->
    <div>
      <h3>Price Range</h3>
      <input type="number" v-model.number="filterState.priceRange[0]" placeholder="Min" />
      <input type="number" v-model.number="filterState.priceRange[1]" placeholder="Max" />
    </div>

    <!-- 评分筛选 -->
    <div>
      <h3>Rating</h3>
      <select v-model.number="filterState.minRating">
        <option :value="0">All</option>
        <option value="1">1+ Stars</option>
        <option value="2">2+ Stars</option>
        <option value="3">3+ Stars</option>
        <option value="4">4+ Stars</option>
        <option value="5">5 Stars</option>
      </select>
    </div>

    <!-- 搜索框 -->
    <div>
      <h3>Search</h3>
      <input v-model="filterState.searchQuery" placeholder="Search products..." />
    </div>

    <!-- 结果展示 -->
    <div class="results">
      <h2>Results ({{ filteredProducts.length }})</h2>
      <table>
        <thead>
          <tr>
            <th>Name</th>
            <th>Category</th>
            <th>Price</th>
            <th>Rating</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="product in filteredProducts" :key="product.id">
            <td>{{ product.name }}</td>
            <td>{{ product.category }}</td>
            <td>${{ product.price }}</td>
            <td>{{ product.rating }}</td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive, computed, watch, onMounted, onBeforeUnmount } from 'vue'

// 1. 原始数据
const products = ref([
  { id: 1, name: 'Smartphone', category: 'Electronics', price: 599, rating: 4.5 },
  { id: 2, name: 'Laptop', category: 'Computers', price: 999, rating: 4.7 },
  { id: 3, name: 'Headphones', category: 'Electronics', price: 199, rating: 4.3 },
  { id: 4, name: 'Coffee Maker', category: 'Home Appliances', price: 89, rating: 4.1 },
  { id: 5, name: 'Sneakers', category: 'Fashion', price: 129, rating: 4.6 }
])

// 2. 提取唯一分类列表
const categories = computed(() => {
  return [...new Set(products.value.map(p => p.category))].sort()
})

// 3. 筛选条件状态
const filterState = reactive({
  categories: [],
  priceRange: [0, 1000],
  minRating: 0,
  searchQuery: ''
})

// 4. 计算筛选结果
const filteredProducts = computed(() => {
  return products.value.filter(product => {
    // 分类筛选
    if (filterState.categories.length && !filterState.categories.includes(product.category)) {
      return false
    }
    
    // 价格区间筛选
    if (product.price < filterState.priceRange[0] || product.price > filterState.priceRange[1]) {
      return false
    }
    
    // 评分筛选
    if (filterState.minRating > 0 && product.rating < filterState.minRating) {
      return false
    }
    
    // 搜索关键字匹配
    if (filterState.searchQuery && !product.name.toLowerCase().includes(filterState.searchQuery.toLowerCase())) {
      return false
    }
    
    return true
  })
})

// 5. 监听筛选条件变化
let searchTimeout = null

watch(
  () => filterState,
  (newVal, oldVal) => {
    console.log('筛选条件更新:', newVal)
    // 保存筛选条件到本地存储
    localStorage.setItem('filterState', JSON.stringify(newVal))
  },
  { deep: true }
)

watch(
  () => filterState.searchQuery,
  (query) => {
    // 防抖处理搜索请求
    clearTimeout(searchTimeout)
    searchTimeout = setTimeout(() => {
      console.log('搜索关键词:', query)
      // 可在此处调用后端搜索接口
    }, 300)
  },
  { immediate: true }
)

// 6. 生命周期钩子
onMounted(() => {
  // 从本地存储恢复筛选条件
  const savedFilter = JSON.parse(localStorage.getItem('filterState'))
  if (savedFilter) Object.assign(filterState, savedFilter)
  
  // 模拟异步数据加载
  setTimeout(() => {
    products.value = [
      ...products.value,
      { id: 6, name: 'Smartwatch', category: 'Electronics', price: 249, rating: 4.2 }
    ]
  }, 1000)
})

onBeforeUnmount(() => {
  if (searchTimeout) clearTimeout(searchTimeout)
})
</script>

<style scoped>
.filter-panel { display: flex; flex-wrap: wrap; gap: 20px; }
.filter-panel > div { flex: 1 1 200px; background: #f5f5f5; padding: 16px; border-radius: 8px; }
input[type="number"], select { width: 100%; margin-top: 8px; }
table { width: 100%; border-collapse: collapse; margin-top: 16px; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
</style>

最佳实践总结

  1. 性能优化

    • 对大型数据集使用computed缓存计算结果
    • 使用deep:true谨慎监听深层对象变化
    • 对频繁变化的值(如搜索框)添加防抖处理
  2. 状态管理

    • 使用reactive统一管理筛选条件
    • 利用localStorage持久化筛选状态
  3. 生命周期管理

    • onMounted中初始化异步数据
    • 及时清理定时器防止内存泄漏
  4. 代码组织

    • 将复杂逻辑拆分为多个计算属性
    • 使用watch替代事件总线通信
    • 保持模板与逻辑分离原则

通过这个完整的筛选面板案例,你应该能够掌握Vue3中响应式系统的核心功能。建议尝试扩展此组件,添加排序功能、分页显示或集成真实API接口,以进一步巩固所学知识。