airi 项目中的 VueUse `useCached`:用自定义比较器精细控制 ref 缓存更新的完整指南
2026/9/10 21:14:53 网站建设 项目流程

airi 项目中的 VueUseuseCached:用自定义比较器精细控制 ref 缓存更新的完整指南

【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi

useCached是 VueUse 提供的响应式缓存工具,它允许开发者用一个**自定义比较器(custom comparator)**来决定:当源 ref 的值变化时,缓存值究竟应该保留原样,还是跟随更新。本文以 airi 仓库内的 VueUse 技能文档为主体,结合仓库中@vueuse/core的实际应用与shallowRef的使用模式,系统讲解useCached的 API、类型体系、核心语义与实战落地方式,帮助你用它替代手写的watch + 条件赋值逻辑,写出更简洁、可维护的 Vue 3 代码。

一、useCached是什么

useCached是 VueUse 中一个位于Utilities(工具类)分类下的组合式函数,一句话概括它的作用:

Cache a ref with a custom comparator(使用自定义比较器缓存一个 ref)。

它接收一个源 ref 和一个比较器函数,返回一个新的 ref。返回的缓存 ref 不会盲目跟随源值变化,而是先经过比较器判断:只有比较器认为"应该更新"时,缓存值才被同步为新的源值。

在 .agents/skills/vueuse-functions/SKILL.md 的函数索引表中,useCached的调用规则(Invocation)被标记为AUTO,即:在 Vue.js / Nuxt 项目中,当需求匹配时应当自动考虑使用它,而不是自己手写重复的缓存判断逻辑——这正是该技能文件倡导"优先使用 VueUse composable 而非自造代码"原则的直接体现。

二、比较器(Comparator)签名与核心语义

useCached的核心是第二个参数——比较器函数,它的签名是:

(newSourceValue, cachedValue) => boolean

语义约定非常明确:

  • 比较器返回true缓存保持不变(keep the cache as-is);
  • 比较器返回false缓存更新为新的源值(update the cache to the new source value)。

理解这个语义反转非常关键:它和Array.prototype.filter的"保留"直觉一致——返回true代表"当前缓存值得保留",返回false代表"旧缓存应该被替换"。如果你习惯写"相等则更新"的逻辑,务必注意这里的方向是相反的。

比较器的两个参数顺序也容易混淆:第一个参数是新源值(newSourceValue),第二个才是当前缓存值(cachedValue),不要写反。

三、完整使用示例与行为推演

原文档给出了一个非常典型的对象缓存场景,完整继承如下:

import { useCached } from '@vueuse/core' import { shallowRef } from 'vue' interface Data { value: number extra: number } const source = shallowRef<Data>({ value: 42, extra: 0 }) const cached = useCached(source, (newSourceValue, cachedValue) => newSourceValue.value === cachedValue.value) source.value = { value: 42, extra: 1, } console.log(cached.value) // { value: 42, extra: 0 } source.value = { value: 43, extra: 1, } console.log(cached.value) // { value: 43, extra: 1 }

逐步推演这个示例,可以清晰看到比较器的工作过程:

  1. 初始状态source{ value: 42, extra: 0 },缓存值随之初始化为同一份数据。
  2. 第一次更新:源值变为{ value: 42, extra: 1 }。此时比较器执行newSourceValue.value === cachedValue.value,即42 === 42,返回true缓存被保留,仍然是{ value: 42, extra: 0 }extra字段的变更被"过滤"掉了。
  3. 第二次更新:源值变为{ value: 43, extra: 1 }。比较器执行43 === 42,返回false缓存被更新,成为{ value: 43, extra: 1 }

这个模式非常适合只关心对象中某个关键字段、忽略其他噪声字段的场景——例如服务端推送的 JSON 数据带有时间戳、版本号等高频变化字段,但你只希望缓存值在业务主键(value)真正变化时才刷新。

四、类型声明深度解读

原文档给出的完整类型声明如下:

export interface UseCachedOptions<D extends boolean = true> extends ConfigurableDeepRefs<D>, WatchOptions {} export declare function useCached<T, D extends boolean = true>( refValue: Ref<T>, comparator?: (newSourceValue: T, cachedValue: T) => boolean, options?: UseCachedOptions<D>, ): UseCachedReturn<T, D> export type UseCachedReturn< T = any, D extends boolean = true, > = ShallowOrDeepRef<T, D>

逐项拆解:

  • UseCachedOptions<D>:第三个可选参数options的类型,它同时继承了ConfigurableDeepRefs<D>与 Vue 的WatchOptions。这意味着你既能控制"深层/浅层"响应式,也能使用 Vuewatch的完整配置(如flushdeep等)。
  • 泛型D extends boolean = true:控制返回的 ref 是浅层(shallowRef)还是深层(ref)响应式,默认true
  • UseCachedReturn<T, D>:返回类型为ShallowOrDeepRef<T, D>,即根据D的值决定返回ShallowRef<T>还是Ref<T>。由于缓存值与源值默认保持相同的响应式层级,返回类型也因此统一为Ref家族,可以在模板和组合式函数中直接使用。

五、与 VueUse 其他"缓存/历史"工具的辨析

在决定使用useCached之前,需要区分它与 VueUse 中几个容易混淆的邻近工具:

工具核心能力适用场景差异要点
useCached用自定义比较器决定缓存是否跟随源 ref 更新只关心对象关键字段、过滤噪声更新更新策略完全由比较器自定义,无历史记录
useCloned响应式克隆一个 ref需要副本与源解耦、可手动sync()侧重"克隆+同步",不提供比较器语义
useMemoize按函数参数缓存计算结果并保持响应式昂贵计算结果的记忆化缓存键是参数,与 ref 值缓存维度不同
useRefHistory/useManualRefHistory追踪 ref 的变化历史撤销/重做、变更审计保留全部历史快照,不丢弃更新
usePrevious持有 ref 的上一个值需要"上一次值"做差值比较只存上一个值,无过滤语义

对应关系:useCached属于"选择性保留最新值",useRefHistory属于"保留全部历史",usePrevious属于"只留前一个"。如果你的需求是"变化了才更新、没变化就维持原样",useCached是最贴合的工具;如果需要回滚能力,则应选择useRefHistory系列(参考 useRefHistory.md 与 useManualRefHistory.md)。

六、在 airi 仓库中的落地场景

airi 是自托管的 AI 陪伴应用(支持实时语音、Minecraft 游玩,覆盖 Web / macOS / Windows),其前端大量使用 Vue 3 + VueUse 生态。仓库证据如下:

  • 多个前端应用均以pnpm catalog方式统一声明了@vueuse/core依赖:apps/stage-pocket/package.jsonapps/stage-tamagotchi/package.jsonapps/stage-web/package.jsonapps/ui-server-auth/package.jsonapps/component-calling/package.json中均为"@vueuse/core": "catalog:",说明 VueUse 是整个仓库前端共享的基础工具层。
  • 仓库中广泛使用shallowRef管理高频变化的状态。例如 permissions-panel.vue 中用shallowRef承载权限布尔状态,controls-island-root.vue 中用shallowRef<ControlsIslandDock>管理悬浮控制岛的停靠位置。

由此可以推断useCached在 airi 这类项目中典型的适配场景:

  1. 实时语音/音频状态流:音频采样、音量、设备状态等高频 ref(参见 audio-input.ts 与 audio-record.ts)往往携带大量中间变化,可用比较器只保留用户关心的阈值级变化。
  2. 模型/角色状态同步stage-pocketstage-tamagotchi需要同步 3D 模型、Live2D 角色的状态对象,当后端推送的状态中只有部分字段变化时,用useCached过滤无关字段更新,避免触发昂贵的渲染副作用。
  3. UI 配置面板controls-island-*.vue这类控制岛组件中的配置对象,可用比较器忽略extra类的辅助字段,只在核心参数变化时触发重绘。

需要说明:经检索,当前仓库源码中尚未出现useCached的直接调用实例,以上场景是基于仓库现有@vueuse/core依赖、shallowRef使用模式与 composable 编码风格作出的合理推断,落地时请以实际业务需求为准。

七、使用建议与注意事项

结合原文档语义与 VueUse 的工程实践,给出以下使用要点:

  • 配合shallowRef使用:当源数据是对象/数组时,优先用shallowRef承载并按需整体替换,配合useCached的浅层缓存,可显著减少深层响应式代理的开销(VueUse 官方文档示例即采用shallowRef)。
  • 比较器保持纯函数:比较器应只做纯比较,避免在其中写入副作用(如修改缓存值),否则可能引发不可预期的更新循环。
  • 返回值可直接用于模板UseCachedReturn<T, D>是标准的Ref/ShallowRef,在<template>中会自动解包,无需额外处理。
  • 需要历史时换工具:如果"保留旧值"的同时还想支持撤销回滚,请改用useRefHistory/useManualRefHistory;如果只是记忆函数计算结果,请使用useMemoize
  • 遵循技能文件的 AUTO 规则:按照 SKILL.md 的约定,在 Vue.js / Nuxt 开发中遇到"带过滤条件的 ref 缓存"需求时,应优先考虑useCached而非手写watch + 条件赋值,以保证代码的简洁性与可维护性。

八、小结

useCached用一个精炼的 API 解决了响应式世界里一个常见痛点:"如何有选择地缓存最新值"。理解其比较器"返回true保留、返回false更新"的反向语义,掌握ConfigurableDeepRefsWatchOptions组成的选项体系,并将它与useCloneduseMemoizeuseRefHistory等邻近工具正确区分,你就能在 airi 这类复杂 Vue 3 应用中写出更精准、更高效的响应式状态管理代码。完整的函数索引与调用规则可继续查阅 .agents/skills/vueuse-functions/SKILL.md 及 useCached.md 原文档。

【免费下载链接】airi💖🧸 Self hosted, you-owned Grok Companion, a container of souls of waifu, cyber livings to bring them into our worlds, wishing to achieve Neuro-sama's altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询