Vue3核心特性实战:watch、computed与生命周期在筛选面板中的应用
作为一名前端开发者,我将在本文通过构建一个完整的筛选面板组件,深入讲解Vue3中watch、computed和生命周期钩子的使用技巧。我们将从零开始构建一个支持多条件筛选的数据表格,涵盖从基础语法到高级应用场景的完整流程。
目录
环境准备
我们使用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
})
})
关键优化点:
- 将复杂过滤逻辑封装在计算属性中
- 自动依赖追踪,任何筛选条件变化都会触发重新计算
- 保持原始数据的不可变性
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>
最佳实践总结
-
性能优化:
- 对大型数据集使用
computed缓存计算结果 - 使用
deep:true谨慎监听深层对象变化 - 对频繁变化的值(如搜索框)添加防抖处理
- 对大型数据集使用
-
状态管理:
- 使用
reactive统一管理筛选条件 - 利用
localStorage持久化筛选状态
- 使用
-
生命周期管理:
- 在
onMounted中初始化异步数据 - 及时清理定时器防止内存泄漏
- 在
-
代码组织:
- 将复杂逻辑拆分为多个计算属性
- 使用
watch替代事件总线通信 - 保持模板与逻辑分离原则
通过这个完整的筛选面板案例,你应该能够掌握Vue3中响应式系统的核心功能。建议尝试扩展此组件,添加排序功能、分页显示或集成真实API接口,以进一步巩固所学知识。