customRef 是 Vue 3 中用于完全控制 ref 的 getter 和 setter 行为的底层响应式 API,适用于防抖、缓存、localStorage 同步等需精细响应式控制的场景;必须手动调用 track() 和 trigger() 以确保依赖追踪与更新触发正常工作。

customRef 是 Vue 3 组合式 API 中一个底层响应式工具,它让你能**完全控制 ref 的 getter 和 setter 行为**。要实现“带缓存特征的响应式属性”,核心思路是:在 getter 中加入缓存逻辑(比如避免重复计算或重复请求),同时确保依赖追踪和触发更新的机制依然正常工作。
缓存的本质:避免重复执行昂贵操作
所谓“缓存特征”,通常指:当依赖未变时,不重新执行 getter 中的耗时逻辑(如复杂计算、API 调用、DOM 查询等),而是直接返回上次结果。但注意:customRef 本身不自动管理缓存,你需要手动实现缓存存储和失效逻辑。
关键点:
- 缓存值需存在闭包中(如用 let cache 或 ref 存储)
- getter 中判断是否可复用缓存(例如比对依赖是否变化)
- setter 中需主动清空缓存(否则后续 getter 会返回过期值)
- 必须调用 track() 告诉 Vue “我读了这个值”,调用 trigger() 告诉 Vue “这个值变了”
示例:带缓存的防抖计算属性(避免频繁重算)
假设有一个搜索关键词 keyword,你想实时生成高亮 HTML(含防抖+缓存),避免每次 keyword 变化都立刻执行耗时的高亮逻辑:
立即学习“前端免费学习笔记(深入)”;
Vue 3 组合式 API 与 最佳实践,涵盖代码组织、useXxx 模式、组合式函数设计、Store 集成、响应式优化及功能抽取。
import { customRef, ref, watch } from 'vue'
function useCachedHighlight(keyword, debounceMs = 300) {
const cache = ref(null)
const cacheKey = ref('')
// 模拟一个耗时的高亮函数
const highlight = (str) => {
// 实际可能是 DOM 操作或正则替换,这里用 sleep 模拟
return new Promise(resolve => {
setTimeout(() => resolve(`<mark>${str}</mark>`), 100)
})
}
return customRef((track, trigger) => {
let pending = null
return {
get() {
track() // 必须跟踪读取
if (cacheKey.value === keyword.value) {
return cache.value
}
return null // 或 placeholder,表示未就绪
},
set(newVal) {
// 清空缓存 & pending
cache.value = null
cacheKey.value = ''
if (pending) clearTimeout(pending)
pending = setTimeout(async () => {
const result = await highlight(newVal)
cache.value = result
cacheKey.value = newVal
trigger() // 值已更新,通知依赖
}, debounceMs)
}
}
})
}
// 在 setup 中使用:
// const keyword = ref('')
// const highlighted = useCachedHighlight(keyword)
更通用的缓存 ref 工厂(支持依赖数组)
如果缓存依赖多个响应式变量(如 keyword 和 theme),可用以下模式:
function cachedRef(getValue, dependencies) {
let cache = undefined
let cacheDeps = []
return customRef((track, trigger) => ({
get() {
track()
const depsChanged = !cacheDeps.length ||
dependencies.some((dep, i) => dep !== cacheDeps[i])
if (depsChanged) {
cache = getValue()
cacheDeps = dependencies.map(d => d)
}
return cache
},
set(newVal) {
cache = newVal
cacheDeps = []
trigger()
}
}))
}
// 使用示例:
// const keyword = ref('vue')
// const count = ref(5)
// const expensive = cachedRef(
// () => heavyCompute(keyword.value, count.value),
// [keyword.value, count.value]
// )
⚠️ 注意:dependencies 数组里传的是当前值(非 ref),所以需确保它们是响应式源的最新快照;更健壮的做法是传入 ref 列表并在内部用 .value 访问,再配合 watch 或手动 track 依赖。
什么时候该用 customRef?替代方案对比
不是所有缓存场景都需要 customRef:
-
简单计算缓存 → 优先用
computed+ref手动缓存中间结果(更直观) -
异步数据缓存(如 API) → 推荐用
useAsync类库或封装ref+watch+ 状态管理(loading/error/data) - 需要精细控制响应式行为(如节流 getter、条件触发、自定义依赖收集)→ customRef 是正确选择
customRef 的价值不在“缓存”本身,而在于它赋予你绕过默认响应式约束的能力——你可以决定什么时候被追踪、什么时候触发更新、值怎么来、怎么存。

















