第一层:防抖原理与原生实现
1. 核心逻辑
防抖依靠闭包缓存定时器 timer,用户连续输入时每次清空上一次延时、重新计时,仅停止输入延迟 300ms 后发起搜索,大幅减少 Axios 请求次数。
- 必须保留 this 上下文、透传参数;
- 局限性:只控制请求触发频率,无法解决网络延迟带来的竞态问题。
基础防抖工具函数
javascript
// 通用防抖,可在Vue全局导入使用
export function debounce(fn, delay = 300) {
let timer = null
return function(...args) {
clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, args), delay)
}
}
第二层:竞态问题 + Axios 两种解决方案
1. 竞态成因
用户先后输入a、ab,同时发起两条 Axios 请求;网络波动下短关键词请求响应更慢,后发起的请求先返回,旧数据覆盖最新搜索结果,列表渲染错乱。 不推荐 loading 锁,会阻塞连续输入,交互很差。
方案 1:版本序列号标记(兼容所有 Axios 版本)
每次搜索自增序号,接口回调判断当前序号是否为最新,过期响应直接丢弃,不更新页面数据。
javascript
let latestSeq = 0
const search = debounce(async (keyword) => {
const curSeq = ++latestSeq
const res = await axios.get('/api/search', { params: { keyword } })
// 过期请求,抛弃结果
if (curSeq !== latestSeq) return
searchList.value = res.data
})
优点:无版本限制;缺点:无效请求仍会走完网络流程,浪费带宽。
方案 2:AbortController 取消请求(Axios 0.22+ 推荐)
Axios 支持signal中断信号,每次新搜索直接终止上一个未完成请求,从根源杜绝过期响应。
javascript
let controller = null
const search = debounce(async (keyword) => {
// 取消上一轮未完成请求
controller?.abort()
controller = new AbortController()
try {
const res = await axios.get('/api/search', {
params: { keyword },
signal: controller.signal
})
searchList.value = res.data
} catch (err) {
// 主动取消的请求不打印错误
if (!axios.isCancel(err)) console.error('搜索失败', err)
}
})
优点:直接中断网络,减少服务器压力;缺点:仅支持 Axios0.22 以上版本。
第三层:Vue3 工程化封装(组合式 useDebounceSearch 工具函数)
Vue3 中使用ref/shallowRef存储定时器与 AbortController 实例,封装成组合式函数 (composable),代替 React Hook,自动处理组件卸载资源清理,防止内存泄漏。
完整 composable 代码 useDebounceSearch.js
javascript
import { shallowRef, onUnmounted, useCallback } from 'vue'
import axios from 'axios'
export function useDebounceSearch(delay = 300) {
// shallowRef 存储复杂实例,不触发多余响应式更新
const timer = shallowRef(null)
const abortCtrl = shallowRef(null)
// 防抖搜索处理函数
const handleSearch = useCallback(async (keyword, setResult) => {
// 清除旧定时器
if (timer.value) clearTimeout(timer.value)
timer.value = setTimeout(async () => {
// 终止上一次未完成请求
abortCtrl.value?.abort()
const controller = new AbortController()
abortCtrl.value = controller
try {
const res = await axios.get('/api/search', {
params: { keyword },
signal: controller.signal
})
setResult(res.data)
} catch (err) {
if (!axios.isCancel(err)) console.error(err)
}
}, delay)
}, [delay])
// 组件卸载:统一清理定时器、中断请求
onUnmounted(() => {
timer.value && clearTimeout(timer.value)
abortCtrl.value?.abort()
})
return { handleSearch }
}
Vue3 组件内使用示例
javascript
<script setup>
import { ref } from 'vue'
import { useDebounceSearch } from '@/composables/useDebounceSearch'
const searchList = ref([])
const { handleSearch } = useDebounceSearch(300)
// 输入框绑定
const inputChange = (e) => {
const keyword = e.target.value
handleSearch(keyword, (data) => {
searchList.value = data
})
}
</script>
<template>
<input @input="inputChange" placeholder="搜索商品" />
<ul v-for="item in searchList" :key="item.id">{{ item.name }}</ul>
</template>
四、项目落地场景
- 商城后台管理系统(Vue3 + Vite) 商品列表、订单列表顶部实时搜索框,支持名称、编号模糊检索;用户快速输入、反复修改筛选条件会并发大量 Axios 请求,全局引入封装好的
useDebounceSearch组合函数,统一防抖延时、请求中断逻辑,弹窗 / 页面关闭时自动取消请求,避免控制台报错。 - 企业 OA 文档检索平台 海量文件实时联想搜索,网络波动大,项目 Axios 版本 0.26,优先使用 AbortController 方案,减少无效请求带宽消耗,联想列表不会出现旧数据闪烁覆盖问题。
- 移动端 H5 商品搜索页 移动端输入频繁、网络不稳定,封装组合函数全局复用;若项目 Axios 版本过低,切换序列号标记方案兜底,保证低端机型兼容性。
- 公共搜索组件抽离 将带防抖、竞态处理的输入框封装成全局公共组件
SearchInput.vue,内部直接引入useDebounceSearch,业务页面直接引入使用,无需重复写防抖和请求中断逻辑。
面试口述精简总结(三层递进)
- 防抖依靠闭包缓存定时器,降低输入时 Axios 请求频率,但无法解决网络延迟带来的竞态问题;
- 竞态是后发请求先返回、旧数据覆盖页面,Axios 有两种解决方式:一是版本序列号过滤过期响应,二是 AbortController 主动取消上一轮请求,后者性能更优;
- Vue3 项目里不会零散写逻辑,会封装组合式函数 (composable),用 shallowRef 存储定时器和中断控制器,借助 onUnmounted 生命周期统一清理资源,避免内存泄漏; 落地场景主要是后台管理表格检索、H5 商城搜索、文档实时联想,根据 Axios 版本选择对应竞态方案,抽成公共组合函数 / 组件全局复用。