airi 前端实践:理解与运用 VueUse watchAtMost——带触发次数上限的响应式监听器
【免费下载链接】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
watchAtMost是 VueUse 提供的一个「带触发次数上限」的watch变体:回调函数被执行的总次数受count选项约束,次数用尽后监听器自动停止。在 airi 这个以 Vue 3 构建 Web / Electron / Capacitor 多端前端的 monorepo 中,VueUse 是前端可组合式逻辑的默认基础设施,而本仓库内置的vueuse-functions技能指南正是把「选对 VueUse 函数」写成了团队开发规范。读完本文,你将掌握watchAtMost的完整用法、三个重载签名与返回对象中stop / pause / resume / count四个句柄的语义,并知道在 airi 的多端 Vue 应用中如何正确引入和定位它。
一、在 airi 中,VueUse 函数参考文档如何被组织
airi 仓库在 .agents/skills/vueuse-functions/SKILL.md 中内置了一套「VueUse 函数决策与实现指南」,其定位是:在 Vue.js / Nuxt 开发任务中,先把需求映射到最合适的 VueUse 组合式函数,优先使用组合式函数而非自写代码,以保持实现简洁、可维护、高性能。该指南约定每个函数的详细用法与类型声明都存放在./references目录下对应的 Markdown 文档中,使用任何函数前都应查阅对应参考文档——本文的主体 watchAtMost.md 就是这套参考文档中Watch分类下的一篇。
在 SKILL.md 的 Watch 分类函数表中,watchAtMost的描述为 "watchwith the number of times triggered"(带触发次数限制的watch),调用规则标记为AUTO,含义是「适用时自动使用」。同一分类下还有watchDebounced、watchThrottled、watchPausable、watchIgnorable、watchOnce、whenever等变体,它们共同构成 VueUse 对原生watch的增强矩阵,选型时可按需对照 SKILL.md 的函数表。
依赖层面,airi 通过 pnpm workspace catalog 统一管理 VueUse 版本:pnpm-workspace.yaml 的 catalog 中固定了'@vueuse/core': ^14.4.0与'@vueuse/shared': ^14.4.0,各前端应用(如 apps/stage-web/package.json、apps/stage-tamagotchi/package.json、apps/stage-pocket/package.json、apps/ui-server-auth/package.json、packages/stage-ui/package.json 等)均以"@vueuse/core": "catalog:"的形式引用同一版本。因此watchAtMost在任一 airi 前端包中都能从@vueuse/core直接导入,无需各自声明版本。
二、基本用法:count 选项与自动停止
watchAtMost与原生watch的调用形态几乎一致,唯一新增的是第三个参数对象中的count选项,它声明回调函数最多被触发多少次;达到该次数后,watch 会自动停止,不需要调用方手动stop()。这是它相对原生watch最核心的行为差异。
参考文档给出的标准用法如下(完整继承自 watchAtMost.md):
import { watchAtMost } from '@vueuse/core' watchAtMost( source, () => { console.log('trigger!') }, // triggered it at most 3 times { count: 3, // the number of times triggered }, )语义拆解:
source可以是 Vue 3 中一切合法的 watch 源:Ref、reactive对象、getter 函数,或一个源组成的数组 / 对象映射(对应下文三个重载);- 回调在每次源变化时被执行,最多执行
count次; - 执行满
count次后监听器被自动解除,后续源变化不再触发任何逻辑; - 若源在达到上限前就停止变化(例如组件卸载、
stop()被手动调用),则回调只会执行实际变化的次数,且释放时机不受影响。
一个典型的实际场景是「有限次重试」或「有限次提示」:例如在 airi 这类实时语音/舞台应用中,监听某个连接状态 ref,仅在它从在线切换到离线的前 3 次弹出去抖提示,避免在弱网抖动时反复打扰用户。用原生watch实现需要手写计数器并在回调内stop();watchAtMost把这套样板逻辑收敛为一个count参数。
三、类型声明:三个重载与返回句柄
3.1 选项接口:WatchAtMostOptions
count的类型是MaybeRefOrGetter<number>,这意味着它不仅接受字面量数字,也接受Ref<number>或 getter 函数——触发上限本身可以是响应式的(例如随用户设置的「重试次数」ref 动态取值)。该接口继承自WatchWithFilterOptions<Immediate>,因此immediate、deep、flush等watchWithFilter系列通用的过滤选项同样可用:
export interface WatchAtMostOptions< Immediate, > extends WatchWithFilterOptions<Immediate> { count: MaybeRefOrGetter<number> }3.2 返回对象:WatchAtMostReturn
返回值不是普通的WatchStopHandle,而是一个包含四个成员的返回对象,这也是watchAtMost相对watchOnce(watchOnce.md 中返回的仅是WatchHandle)能力更强的一点:
export interface WatchAtMostReturn { stop: WatchStopHandle pause: () => void resume: () => void count: ShallowRef<number> }各成员含义:
| 成员 | 类型 | 语义 |
|---|---|---|
stop | WatchStopHandle | 立即停止监听,等价于watch的 stop 句柄 |
pause | () => void | 暂停监听:源变化期间不触发回调(次数不消耗) |
resume | () => void | 从暂停状态恢复监听,直到count用尽 |
count | ShallowRef<number> | 已触发次数的浅层 ref,可用于在模板/逻辑中展示「剩余次数」等状态 |
pause/resume的存在使watchAtMost天然带有watchPausable的能力:你可以在某个「静默期」(例如用户正在操作时)暂停计数,而不会白白消耗宝贵的触发配额。count作为ShallowRef暴露出来,则让触发进度成为可观察的响应式状态。
3.3 三个函数重载
文档给出了与 Vue 原生watch对齐的三重载,分别覆盖单一源、多源数组、reactive 对象三种监听形态:
export declare function watchAtMost< T, Immediate extends Readonly<boolean> = false, >( sources: WatchSource<T>, cb: WatchCallback<T, Immediate extends true ? T | undefined : T>, options: WatchAtMostOptions<Immediate>, ): WatchAtMostReturn export declare function watchAtMost< T extends Readonly<MultiWatchSources>, Immediate extends Readonly<boolean> = false, >( sources: [...T], cb: WatchCallback<MapSources<T>, MapOldSources<T, Immediate>>, options: WatchAtMostOptions<Immediate>, ): WatchAtMostReturn export declare function watchAtMost< T extends object, Immediate extends Readonly<boolean> = false, >( sources: T, cb: WatchCallback<MapSources<T>, MapOldSources<T, Immediate>>, options: WatchAtMostOptions<Immediate>, ): WatchAtMostReturn三点值得注意:
options是必填参数。三个重载都没有像watchOnce那样提供可选的options?:,因为count是WatchAtMostOptions的必需字段——「上限」是watchAtMost的语义核心,不存在「不限次」的退化形态(不限次应直接使用原生watch)。Immediate泛型贯穿签名。Immediate extends Readonly<boolean> = false决定回调中value的类型:当immediate: true时首次同步触发,此时旧值为undefined,所以第一个重载的回调类型写作WatchCallback<T, Immediate extends true ? T | undefined : T>。多源重载通过MapSources<T>/MapOldSources<T, Immediate>自动推导「数组形式的新值/旧值」,保持与 Vue 官方 watch 类型行为一致。- reactive 对象重载(第三个)接受
T extends object,配合immediate之外还可叠加deep语义,用于监听整个响应式对象(例如一个角色状态对象)在有限次数内的整体变化。
四、与其他 watch 变体的选型对照
结合 SKILL.md 的 Watch 分类表,可以把watchAtMost放在家族里横向对比,避免选错工具:
| 函数 | 核心语义 | 返回 |
|---|---|---|
watchOnce | 触发一次后自动停止({ once: true }简写) | WatchHandle |
watchAtMost | 触发满count次后自动停止 | WatchAtMostReturn(含 pause/resume/count) |
watchPausable | 可暂停/恢复,不限次数 | 含pause/resume |
watchDebounced/watchThrottled | 对变化做防抖/节流后触发 | 含flush与过滤器控制 |
watchWithFilter | 通用 EventFilter 控制的 watch 基座 | WatchWithFilterReturn |
watchIgnorable | 可忽略由自身赋值引起的触发 | 含ignoreUpdates |
whenever | 监听布尔源为真时触发 | WatchHandle |
从选型上看:只需要一次用watchOnce;需要 N 次且希望期间可暂停、可观测已用次数,用watchAtMost;高频源需要「每 N 毫秒至多一次」而不是「总共 N 次」,应选watchThrottled——注意二者限制的是不同维度(总次数 vs 频率)。
五、在 airi 中使用 watchAtMost 的实践要点
- 导入方式:在 airi 任一前端包内直接
import { watchAtMost } from '@vueuse/core'。由于 pnpm-workspace.yaml 已以 catalog 形式统一版本(@vueuse/core: ^14.4.0),各包package.json中写作"@vueuse/core": "catalog:"即可,不要在子包内另起版本号。 - 生命周期:组合式函数应在 setup 或组合式逻辑顶层调用。返回的
stop是标准WatchStopHandle,在组件卸载等场景可按需手动释放;而达到count上限时释放是自动的。 - 把 count 做成响应式:由于
count: MaybeRefOrGetter<number>,可以把「剩余重试次数」做成一个ref或从配置/用户设置中读取,让上限随状态演化;同时返回值里的count(已触发次数)是ShallowRef,可与业务状态联动展示。 - 计数不耗尽时的组合:需要「有限次数 + 暂停期」的场景,直接组合返回值里的
pause()/resume();需要「有限次数 + 防抖」时,WatchAtMostOptions继承自WatchWithFilterOptions,可叠加过滤行为,这与 SKILL.md 中watchWithFilter作为通用过滤基座的定位一致。 - 遵循团队技能指南:airi 的
vueuse-functions技能明确要求「使用任何函数前查阅./references中对应文档的 Usage 与 Type Declarations」,本文即基于 watchAtMost.md 原文的 Usage 与 Type Declarations 两节展开;如需确认同一分类下相邻函数(如 watchOnce.md)的细节,可继续查阅 references 目录中的对应文档。
小结
watchAtMost的价值在于用一行count配置替代「手写计数器 + 手动 stop」的样板代码,同时通过WatchAtMostReturn返回的stop / pause / resume / count四件套,把「次数预算」变成了可暂停、可观测、可响应式调整的一等状态。在 airi 这类 Vue 3 + VueUse 深度集成的 monorepo 中,它是实现「有限次触发」类响应式逻辑(有限重试、有限提示、有限自动纠正等)时应当优先考虑的即插即用方案;具体版本与签名以 pnpm-workspace.yaml 固定的@vueuse/core ^14.4.0及 watchAtMost.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),仅供参考