customRef 更适合防抖是因为它能手动控制 track 和 trigger 时机:输入时只 track 不 trigger,防抖结束才 trigger 更新,避免中间值触发响应式副作用。

用 customRef 实现搜索框防抖,核心是**绕过 Vue 默认的响应式追踪机制,手动控制依赖收集与触发更新**,从而在防抖期间不触发视图重渲染。它比用 watch + setTimeout 更精准——只让最终值响应,中间输入完全不触发副作用。
为什么 customRef 比 ref + watch 更适合防抖?
customRef 允许你定义 track(谁读取时收集依赖)和 trigger(谁修改时通知更新)的时机。防抖场景下,你可以:
- 输入时只 track(让组件能读取当前值),但不 trigger;
- 防抖结束、值真正变化时,才调用 trigger,通知组件更新。
这样,中间几十次输入不会引发任何计算属性重新求值或模板重新渲染。
手写一个 debounceRef 工具函数
以下是一个可复用的防抖响应式引用实现:
(Vue 3 Composition API 环境)
import { customRef, nextTick } from 'vue'
<p>export function debounceRef(value, delay = 300) {
let timeout = null
return customRef((track, trigger) => {
return {
get() {
track() // 告诉 Vue:当前副作用依赖这个 ref
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger() // 值稳定后,才通知更新
}, delay)
}
}
})
}在组件中使用 debounceRef
直接替换普通 ref,无需额外 watch 或事件处理逻辑:
import { defineComponent } from 'vue'
import { debounceRef } from './debounceRef'
<p>export default defineComponent({
setup() {
// 创建带 500ms 防抖的响应式搜索关键词
const keyword = debounceRef('', 500)</p><pre class="brush:php;toolbar:false;">// 这个计算属性只在防抖结束后才重新执行
const filteredList = computed(() => {
return list.value.filter(item =>
item.title.includes(keyword.value)
)
})
return () => (
<div>
<input
v-model={keyword.value}
placeholder="输入搜索关键词..."
/>
<ul>
{filteredList.value.map(item => (
<li key={item.id}>{item.title}</li>
))}
</ul>
</div>
)} })
✅ 效果:用户快速连打 “reactive”,只有最后停顿 500ms 后,filteredList 才重新计算并更新列表;中间所有输入都不触发任何响应式副作用。
注意事项与增强建议
-
避免内存泄漏:组件卸载前需清理未执行的定时器。可在
onBeforeUnmount中clearTimeout(timeout),或改用onInvalidate(Vue 3.4+ 支持)自动清理; -
支持立即执行:如需首次输入立刻触发(比如“回车立即搜”),可在
set中加判断,对空→非空或特定按键做即时trigger; -
暴露 cancel 方法:返回对象可扩展
cancel()方法,方便手动中断防抖(例如切换 tab 时清空待执行逻辑); -
搭配 useDebounceFn 更灵活:若逻辑复杂(如要发请求),建议用
useDebounceFn封装函数,再配合普通ref控制状态,比全包在customRef里更易测试和复用。

















