Vue3 Hooks初探:手把手实现筛选面板组件
前言
Vue3 的组合式 API(Composition API)带来了更灵活的代码组织方式,其中 Hooks 机制是核心特性之一。本文通过构建一个功能完整的筛选面板组件,从基础概念到实际应用,逐步深入讲解 Vue3 Hooks 的使用技巧,帮助开发者掌握响应式数据、计算属性、生命周期钩子等核心功能。
一、Vue3 Hooks 基础概念
1. 什么是 Hooks?
在 Vue3 中,Hooks 是指通过组合式 API 定义的函数,用于在组件中复用逻辑。Hooks 函数通常以 use 开头命名(如 useState、useFetch),其本质是封装了响应式数据(ref/reactive)、计算属性(computed)、生命周期钩子(onMounted)等功能的函数。
2. Hooks 的核心特性
- 模块化:将组件逻辑拆分为独立函数,提升复用性。
- 响应式:基于
ref和reactive实现数据驱动视图更新。 - 生命周期集成:通过
onMounted、onUnmounted等钩子处理副作用。 - 组合能力:多个 Hooks 可自由组合,形成复杂逻辑。
二、筛选面板需求分析
我们需要实现一个支持以下功能的筛选面板:
- 分类筛选:多选类别(如电子产品、服装)。
- 价格区间:滑动条选择最低和最高价格。
- 评分筛选:单选按钮选择最低评分。
- 实时搜索:输入关键词过滤商品名称。
- 结果展示:根据筛选条件动态渲染商品列表。
三、代码实现步骤
1. 环境准备
使用 Vite 创建 Vue3 项目:
npm init vite@latest vue3-filter-panel --template vue
cd vue3-filter-panel
npm install
2. 基础组件结构
创建 FilterPanel.vue 组件,定义基础状态:
<template>
<div class="filter-panel">
<!-- 分类筛选 -->
<div>
<h3>Categories</h3>
<div v-for="category in categories" :key="category">
<input type="checkbox" :id="'cat-' + category" :value="category" v-model="filterState.categories" />
<label :for="'cat-' + category">{{ category }}</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 } from 'vue'
// 原始数据
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 }
])
// 筛选条件状态
const filterState = reactive({
categories: [], // 选中的分类
priceRange: [0, 1000], // 价格区间
minRating: 0, // 最低评分
searchQuery: '' // 搜索关键词
})
// 计算筛选后的商品列表
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
})
})
</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. 响应式数据管理
refvsreactive:ref用于单一数据(如searchQuery)。reactive用于对象或数组(如filterState),便于整体响应式追踪。
- 示例:
filterState使用reactive定义,所有筛选条件变化均会自动触发计算属性更新。
2. 计算属性(computed)
- 作用:综合所有筛选条件,返回过滤后的数据。
- 优势:自动缓存计算结果,仅在依赖项变化时重新计算。
- 示例:
filteredProducts通过链式过滤条件生成最终结果。
3. 生命周期钩子
onMounted:模拟异步数据加载。onMounted(() => { // 模拟 API 请求 setTimeout(() => { products.value = [...products.value, { id: 6, name: 'Smartwatch', category: 'Electronics', price: 249, rating: 4.2 }] }, 1000) })onBeforeUnmount:清理定时器,防止内存泄漏。
4. 实时搜索防抖(watch)
- 问题:搜索输入频繁触发计算属性,造成性能浪费。
- 解决方案:使用
watch监听searchQuery,添加防抖处理。let searchTimeout = null watch( () => filterState.searchQuery, (query) => { clearTimeout(searchTimeout) searchTimeout = setTimeout(() => { console.log('Search query:', query) // 可在此调用后端 API }, 300) }, { immediate: true } )
五、优化与扩展
1. 封装自定义 Hooks
将筛选逻辑抽象为可复用的函数:
// useFilter.js
import { reactive, toRefs } from 'vue'
export function useFilter() {
const state = reactive({
categories: [],
priceRange: [0, 1000],
minRating: 0,
searchQuery: ''
})
return toRefs(state)
}
在组件中使用:
const { categories, priceRange, minRating, searchQuery } = useFilter()
2. 持久化筛选条件
利用 localStorage 保存用户选择:
watch(
() => filterState,
(newVal) => { localStorage.setItem('filterState', JSON.stringify(newVal)) },
{ deep: true }
)
在 onMounted 中恢复状态:
onMounted(() => {
const saved = JSON.parse(localStorage.getItem('filterState'))
if (saved) Object.assign(filterState, saved)
})
3. 异步数据加载
将 products 改为从 API 获取:
const products = ref([])
onMounted(async () => {
products.value = await fetchDataFromAPI()
})
六、完整代码示例
完整代码已整合至前述 FilterPanel.vue,关键逻辑总结如下:
- 响应式状态:
filterState管理所有筛选条件。 - 计算属性:
filteredProducts实现多条件联合过滤。 - 生命周期钩子:
onMounted模拟异步数据加载。 - 防抖处理:
watch监听搜索输入,避免频繁计算。 - 持久化存储:
localStorage保存用户筛选状态。
七、最佳实践总结
- 模块化逻辑:将复杂逻辑拆分为独立 Hooks,提升复用性。
- 合理使用
reactive:对对象或数组使用reactive,单一数据用ref。 - 生命周期管理:在
onMounted处理副作用,onBeforeUnmount清理资源。 - 性能优化:对高频操作(如搜索)使用防抖或节流。
- 命名规范:Hooks 函数以
use开头,明确功能意图。
通过本文的筛选面板案例,你可以掌握 Vue3 Hooks 的核心用法,并将其扩展到更复杂的场景中。建议尝试以下练习:
- 添加排序功能(如按价格升序/降序)
- 集成分页功能
- 将筛选逻辑封装为独立 Hooks 并在其他组件中复用