customRef是Vue 3暴露的底层响应式控制接口,需手动调用track()进行依赖收集、trigger()触发更新;其核心在于交由开发者精准控制何时被读、何时通知变更,从而实现防抖、节流等高级逻辑。

customRef 不是“封装好的 ref”,而是 Vue 响应式系统暴露给开发者的底层控制接口。它不帮你做依赖收集或更新通知,而是把 track 和 trigger 这两个关键动作交到你手上——你决定什么时候被谁读、什么时候通知谁改。
track 和 trigger 到底在做什么?
它们不是普通函数,而是响应式系统内部的“通信信令”:
- track():告诉当前活跃的 effect(比如组件渲染函数、computed 或 watch),「我现在正在读这个值,请把我记进它的依赖列表」;
- trigger():通知所有已记录的 effect,「这个值变了,请重新执行」。
Vue 默认的 ref 在 get 时自动 track、set 时自动 trigger;customRef 则完全交由你显式调用——这正是实现防抖、节流、延迟同步、条件触发等逻辑的根基。
为什么不能只改 value,必须手动 trigger?
因为 Vue 不会监听闭包变量的变化。你在 customRef 工厂函数里用 let value = initialValue 存值,这个 value 是纯 JS 变量,Vue 对它一无所知。只有当你在 set 中调用 trigger(),系统才意识到「该通知订阅者了」。
常见误区:
❌ 在 set 里只更新 value,却不调 trigger → 视图永远不更新;
❌ 在 get 里漏掉 track → 即使后续 trigger,也没有 effect 被通知(因为没人登记过)。
一个防抖 ref 的最小可行实现
核心就三步:清旧定时器、设新定时器、定时器结束时更新值并 trigger:
function useDebouncedRef(value, delay = 300) {
let timeout
return customRef((track, trigger) => ({
get() {
track() // 每次读取都声明“我在用这个值”
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger() // 只有这时,才真正“发布变更”
}, delay)
}
}))
}
注意:这里的 value 是闭包变量,track/trigger 是 Vue 注入的钩子——你没动响应式内核,只是在它预留的插槽里填了逻辑。
还能怎么玩?几个真实可落地的方向
- 条件触发:只在 newValue 符合某规则(如非空、长度 > 2)时才 trigger;
- 多源同步:set 时不仅更新本地 value,还同步发请求,等接口返回后再 trigger(模拟乐观更新);
- 计算缓存:get 时 track,但 set 后不立即 trigger,而是标记“脏”,等首次 get 时再计算+trigger;
- 跨组件广播:trigger 时不只通知本组件,还主动调用外部事件总线 emit,实现轻量状态广播。
这些都不是“黑魔法”,只是把 track 和 trigger 当作信号开关,按需组合。

















