customRef 实现防抖比 watch + setTimeout 更合适,因为它能拦截 ref 赋值过程,在 setter 中暂存新值、清除旧定时器并延迟更新 currentValue,使组件读取的值本身就是防抖后的结果,而非仅延迟执行副作用。

Vue 3 的 customRef 是一个底层响应式 API,允许你完全控制 ref 的 getter 和 setter 行为,非常适合实现带防抖、节流、懒加载等自定义逻辑的响应式数据。
为什么用 customRef 实现防抖比 watch + setTimeout 更合适?
直接在 watch 中用 setTimeout 只能监听变化并延迟执行副作用(比如发请求),但无法让响应式值本身“延迟更新”——用户输入时,ref.value 仍是实时变的。而 customRef 能拦截赋值过程,在 setter 中暂存新值、清除旧定时器、延迟触发真正的更新,从而让组件内读取到的值本身就是防抖后的结果。
一个可复用的防抖 ref 工厂函数
下面是一个通用的 useDebouncedRef 实现,接收初始值和防抖毫秒数:
import { customRef, nextTick } from 'vue'
export function useDebouncedRef(value, delay = 200) {
let timeout = null
let currentValue = value
return customRef((track, trigger) => ({
get() {
track() // 告诉 Vue 这个 ref 被读取了
return currentValue
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
currentValue = newValue
trigger() // 通知依赖更新
}, delay)
}
}))
}
在组件中使用示例
比如绑定搜索关键词输入框,只在用户停顿 300ms 后才更新搜索关键词:
立即学习“前端免费学习笔记(深入)”;
<template>
<input v-model="debouncedKeyword" placeholder="输入关键词..." />
<p>当前搜索词:{{ debouncedKeyword }}</p>
</template>
<script setup>
import { useDebouncedRef } from './useDebouncedRef'
const debouncedKeyword = useDebouncedRef('', 300)
</script>
注意:v-model 会自动读写 ref,所以它会正确触发 get 和 set,无需额外处理。
注意事项与增强点
-
初始值同步性:
currentValue初始化即生效,首次读取不会触发异步延迟 -
清理时机:组件卸载前应手动
clearTimeout(timeout),可在onBeforeUnmount中补充(当前示例未包含,实际项目建议加上) -
支持立即执行:可扩展参数,增加
immediate: boolean控制首次输入是否立刻更新(需在 set 中判断) -
兼容 ref 解构:返回的是 ref 对象,支持解构为
{ value },也支持toRefs等组合式 API









