在 Vue3 中使用 debounce 实现搜索框防抖
在现代 Web 应用中,搜索框是高频交互组件。用户每输入一个字符就触发一次 API 请求,不仅浪费资源,还会导致界面卡顿。使用 防抖(Debounce) 技术可以有效解决这一问题。
本文将展示如何在 Vue3 中结合自定义的 debounce 函数,实现一个高性能的防抖搜索功能。
1. 防抖原理回顾
防抖(Debounce) 的核心思想是: 在用户停止操作一段时间后,才执行函数。
例如:
- 用户输入 "hello",共 5 次
input事件 - 每次输入后重置 300ms 计时器
- 只有在最后一次输入后 300ms 内无新输入,才调用
searchAPI
这能将 5 次请求减少为 1 次,极大提升性能。
2. 自定义 debounce 函数(TypeScript 版)
ts
// src/utils/debounce.ts
export function debounce<T extends (...args: any[]) => any>(
fn: T,
wait: number
): T & { cancel: () => void } {
let timeout: ReturnType<typeof setTimeout> | null = null;
const debounced = function (this: any, ...args: any[]) {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(this, args);
timeout = null;
}, wait);
};
// 提供取消功能
debounced.cancel = () => {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
};
return debounced as T & { cancel: () => void };
}3. 在 Vue3 组件中使用
示例:防抖搜索组件
text
<!-- SearchBox.vue -->
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { debounce } from '@/utils/debounce';
// 模拟搜索 API
const searchAPI = async (query: string) => {
console.log('🔍 搜索请求:', query);
// 模拟网络请求
await new Promise(resolve => setTimeout(resolve, 500));
return [`结果1: ${query}`, `结果2: ${query}`];
};
// 搜索关键词
const keyword = ref('');
// 搜索结果
const results = ref<string[]>([]);
// 加载状态
const loading = ref(false);
// 创建防抖函数(300ms)
const debouncedSearch = debounce(async (query: string) => {
if (!query.trim()) {
results.value = [];
loading.value = false;
return;
}
loading.value = true;
try {
const data = await searchAPI(query);
results.value = data;
} catch (err) {
console.error('搜索失败', err);
} finally {
loading.value = false;
}
}, 300);
// 监听 keyword 变化,触发防抖搜索
const handleInput = () => {
debouncedSearch(keyword.value);
};
// 组件卸载前取消未完成的请求(可选)
onBeforeUnmount(() => {
debouncedSearch.cancel(); // 取消待执行的搜索
});
// 可选:组件挂载后聚焦输入框
onMounted(() => {
const input = document.getElementById('search-input');
input?.focus();
});
</script>
<template>
<div class="search-container">
<input
id="search-input"
v-model="keyword"
@input="handleInput"
type="text"
placeholder="输入关键词搜索..."
class="search-input"
:disabled="loading"
/>
<div v-if="loading" class="loading">搜索中...</div>
<ul v-if="results.length > 0" class="results">
<li v-for="(result, index) in results" :key="index">
{{ result }}
</li>
</ul>
<p v-else-if="!loading && keyword" class="no-results">
无匹配结果
</p>
</div>
</template>
<style scoped>
.search-container {
max-width: 500px;
margin: 20px auto;
padding: 20px;
font-family: Arial, sans-serif;
}
.search-input {
width: 100%;
padding: 10px;
font-size: 16px;
border: 1px solid #ddd;
border-radius: 4px;
outline: none;
}
.search-input:focus {
border-color: #007bff;
}
.loading {
color: #666;
font-style: italic;
margin-top: 10px;
}
.results {
list-style: none;
padding: 0;
margin-top: 10px;
}
.results li {
padding: 8px;
background: #f9f9f9;
border: 1px solid #eee;
margin: 4px 0;
border-radius: 4px;
}
.no-results {
color: #999;
margin-top: 10px;
}
</style>4. 关键点解析
为什么使用 @input 而不是 v-model 的 @change?
@input:每次输入(包括删除、粘贴)都触发,实时响应@change:仅在输入框失去焦点或回车时触发,不适合搜索
为什么 debounce 返回函数而不是直接调用?
ts
const debouncedSearch = debounce(searchAPI, 300)- 返回的是一个“包装后”的函数
- 可以在多个地方复用
- 支持
cancel()取消
为什么在 onBeforeUnmount 中调用 cancel()?
- 防止组件销毁后,延迟的搜索回调试图更新已卸载的组件状态
- 避免内存泄漏和
Cannot read property 'value' of null错误
5. 进阶优化建议
(1) 结合 watch 实现更简洁的写法
ts
import { watch } from 'vue';
watch(keyword, (newVal) => {
if (newVal) {
debouncedSearch(newVal);
} else {
results.value = [];
}
});(2) 添加取消上一次请求的能力(使用 AbortController)
ts
let controller: AbortController | null = null;
const debouncedSearch = debounce(async (query: string) => {
// 取消上一次请求
if (controller) controller.abort();
controller = new AbortController();
try {
const res = await fetch(`/api/search?q=${query}`, {
signal: controller.signal
});
const data = await res.json();
results.value = data;
} catch (err) {
if (err.name !== 'AbortError') {
console.error('搜索失败', err);
}
}
}, 300);6. 效果演示
| 用户输入序列 | 传统方式 | 防抖方式(300ms) |
|---|---|---|
| h → he → hel → hell → hello | 5 次请求 | 1 次请求("hello") |
| a → ab → abc → (停顿 500ms) → abcd | 4 次请求 | 2 次请求("abc", "abcd") |
结语:防抖是前端性能优化的基石
在 Vue3 中使用 debounce 实现搜索防抖,不仅能:
- 减少服务器压力
- 提升用户体验
- 降低网络消耗
更重要的是,它体现了你对性能和用户体验的极致追求。
当你写下:
ts
const debouncedSearch = debounce(searchAPI, 300)你不仅是在调用一个工具函数,更是在为用户打造一个流畅、高效、专业的应用。
这才是现代前端开发的真正价值。