防抖 customRef 必须手动调用 track() 和 trigger() 来维持响应式依赖追踪与视图更新,定时器需闭包隔离,并支持 cancel/flush 方法及组件卸载时清理。

在 Vue 3 的 Composition API 中,用 customRef 封装防抖 ref,核心是让 v-model 绑定的输入框“看起来实时响应”,实际值延迟更新,同时保持响应式依赖正常追踪。关键不在延迟赋值本身,而在于控制何时触发视图更新和依赖通知。
防抖 customRef 必须手动管理 track 和 trigger
customRef 不像普通 ref() 那样自动处理依赖,它把读写控制权完全交给你。所以每次 get 时要调用 track() 告诉 Vue “这里有人在读这个值”,每次真正更新值后必须调用 trigger() 告诉 Vue “值变了,该更新了”。漏掉任一环节,模板就不会响应。
- 不调
track()→ 模板首次渲染后不再监听变化 - 不调
trigger()→ 即使内部值变了,视图也不刷新 - 定时器变量(如
timer)必须声明在工厂函数闭包内,确保每次 ref 独立隔离
标准防抖 customRef 实现(带 cancel/flush 支持)
一个健壮的防抖 ref 应支持取消待执行更新、立即提交当前值。下面是最简可用版本:
import { customRef } from 'vue'
export function debounceRef(value, delay = 300) {
let timer = null
return customRef((track, trigger) => ({
get() {
track()
return value
},
set(newValue) {
clearTimeout(timer)
timer = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}))
}
使用方式直接:const searchQuery = debounceRef('', 500),然后在模板中 <input v-model="searchQuery"> 即可生效——v-model 会自动调用 .value 的 getter/setter,无需额外适配。
立即学习“前端免费学习笔记(深入)”;
增强版:支持 cancel 和 flush 方法
如果需要主动干预(比如用户点击搜索按钮时立刻提交,或离开页面前清空未执行的更新),可在返回对象上挂载方法:
export function useDebouncedRef(value, delay = 300) {
let timer = null
const ref = customRef((track, trigger) => ({
get() {
track()
return value
},
set(newValue) {
clearTimeout(timer)
timer = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}))
// 扩展方法
ref.cancel = () => clearTimeout(timer)
ref.flush = () => {
clearTimeout(timer)
trigger() // 强制通知当前值已就绪
}
return ref
}
组件内可这样用:searchQuery.cancel() 中断延迟;searchQuery.flush() 立即生效并触发更新。
注意组件卸载时自动清理定时器
如果防抖 ref 生命周期长于组件(比如定义在 composable 里被多个组件复用),需配合 onUnmounted 清理定时器,避免内存泄漏:
- 在创建 ref 的 setup 或 composable 内,用
onUnmounted(() => clearTimeout(timer)) - 或者更稳妥地,在
flush和cancel方法里也做 clearTimeout,形成双重保障 - 不要依赖组件 unmount 自动清理 —— 定时器是闭包变量,Vue 不会帮你回收


















