airi VueUse Composable 深度指南:用 watchIgnorable 精确忽略指定响应式更新(含 flush 时序与 ignorePrevAsyncUpdates 解析)
【免费下载链接】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
在 airi 这类重度使用 Vue 3 响应式系统的前端工程中,watch回调常常需要区分「用户/外部行为引起的变更」与「程序自身写回引起的变更」。本文以仓库中 .agents/skills/vueuse-functions/references/watchIgnorable.md 为骨架,完整讲解 VueUse 的watchIgnorablecomposable:它的ignoreUpdates与ignorePrevAsyncUpdates两个控制句柄如何精确压制特定的源更新、flush时序如何影响其语义、完整类型签名与废弃别名ignorableWatch,并结合 airi 仓库的依赖版本给出适用前提。
一、watchIgnorable 的定位与文档来源
watchIgnorable是 VueUse 对原生watch的扩展:它在普通watch的基础上额外返回ignoreUpdates(updater)和ignorePrevAsyncUpdates()两个句柄,用于忽略对源(source)的特定更新,使 watch 回调不针对这些更新触发。
在 airi 仓库中,该函数由 AI 编码技能体系作为 Watch 分类下的标准能力收录。技能总览 .agents/skills/vueuse-functions/SKILL.md 的 Watch 分类表格中标注:
| Function | Description | Invocation |
|---|---|---|
watchIgnorable | Ignorable watch | AUTO |
其中AUTO表示「适用时自动优先选用 VueUse composable 而非自研代码」。该引用文档由上游 VueUse 官方技能仓库同步而来,同步信息记录在 .agents/skills/vueuse-functions/SYNC.md(Source 为vendor/vueuse/skills/vueuse-functions,同步日期 2026-06-22),因此其中的用法与类型签名可直接视为该版本的权威参考。
版本适用前提:airi 采用 pnpm workspace 统一目录管理依赖,pnpm-workspace.yaml 中 catalog 声明'@vueuse/core': ^14.4.0,而 Vue 版本为^3.5.41(pnpm-workspace.yaml)。apps/、packages/下各前端应用(如 stage-web、stage-tamagotchi、stage-ui 等)均通过"@vueuse/core": "catalog:"引用同一版本,因此在这些应用中导入@vueuse/core即可直接使用watchIgnorable。
二、ignoreUpdates:忽略指定同步代码块内的源更新
原始用法定义(对应 引用文档 的 Usage 一节)如下:
Extended
watchthat returns extraignoreUpdates(updater)andignorePrevAsyncUpdates()to ignore particular updates to the source.
import { watchIgnorable } from '@vueuse/core' import { nextTick, shallowRef } from 'vue' const source = shallowRef('foo') const { stop, ignoreUpdates } = watchIgnorable( source, v => console.log(`Changed to ${v}!`), ) source.value = 'bar' await nextTick() // logs: Changed to bar! ignoreUpdates(() => { source.value = 'foobar' }) await nextTick() // (nothing happened) source.value = 'hello' await nextTick() // logs: Changed to hello! ignoreUpdates(() => { source.value = 'ignored' }) source.value = 'logged' await nextTick() // logs: Changed to logged!结合 watchPausable 参考文档 与 Vuewatch的通用语义,可以逐步解读这段代码的执行时序:
| 步骤 | 操作 | 结果 | 说明 |
|---|---|---|---|
| 1 | source.value = 'bar'后await nextTick() | 打印Changed to bar! | 默认flush: 'pre'下,watch 回调在组件更新前的下一个 tick 执行 |
| 2 | ignoreUpdates(() => { source.value = 'foobar' })后await nextTick() | 无任何输出 | 闭包内对 source 的写入被完全压制,watch 任务不会入队执行 |
| 3 | source.value = 'hello'后await nextTick() | 打印Changed to hello! | 忽略作用域外的正常变更照常触发回调 |
| 4 | 先ignoreUpdates(() => { source.value = 'ignored' }),再在作用域外source.value = 'logged' | 仅打印Changed to logged! | 即使同一次 tick 内存在一次「被忽略的写入」和一次「正常写入」,watch 只响应后者 |
第 4 步是理解ignoreUpdates的关键:忽略是按更新(update)粒度而非按 tick 粒度生效的。它接受一个同步 updater 函数,仅该函数体内产生的源变更被屏蔽;紧随其后的正常赋值依然能触发回调。这与「用一个布尔标志位包裹整个 watcher」的常见手写方案不同——ignoreUpdates的作用域精确、结构化,不存在标志位忘复位、重入覆盖等易错点。
解构返回对象中的stop与原生watch一致,是标准的WatchStopHandle,用于在组件卸载或作用域销毁时停止监听。
三、flush 时序:与 watch 完全一致,默认 'pre'
引用文档专门用一节说明 flush 时序(.agents/skills/vueuse-functions/references/watchIgnorable.md):
watchIgnorableaccepts the same options aswatchand uses the same defaults. So, by default the composable works usingflush: 'pre'.
即:
- 第三个参数
options类型为WatchWithFilterOptions<Immediate>——与 watchWithFilter 参考文档 中的WatchWithFilterOptions定义一致,在原生WatchOptions基础上叠加eventFilter能力; - 未显式指定
flush时默认为'pre',回调在 DOM 更新前执行; immediate: true、deep、once等原生选项均可透传,行为与watch相同。
flush的取值直接决定下一节ignorePrevAsyncUpdates是否有效,因此理解这一节是理解该 API 语义的前提。
四、ignorePrevAsyncUpdates:丢弃上一轮已入队的异步更新
ignorePrevAsyncUpdates()解决的是另一类问题:某个变更已经被记录、watch 任务已排入当前 tick 的队列,但在回调真正执行前你决定「算了,这次不用响应了」。
原始示例(对应 引用文档):
import { watchIgnorable } from '@vueuse/core' import { nextTick, shallowRef } from 'vue' const source = shallowRef('foo') const { ignorePrevAsyncUpdates } = watchIgnorable( source, v => console.log(`Changed to ${v}!`), ) source.value = 'bar' await nextTick() // logs: Changed to bar! source.value = 'good' source.value = 'by' ignorePrevAsyncUpdates() await nextTick() // (nothing happened) source.value = 'prev' ignorePrevAsyncUpdates() source.value = 'after' await nextTick() // logs: Changed to after!逐步解读:
source.value = 'bar'→ 正常触发Changed to bar!;- 同步连续赋值
'good'、'by'后调用ignorePrevAsyncUpdates():此时 watch 的回调任务尚未执行(flush: 'pre'下要到 tick 末尾才跑),调用该句柄把已排队的这次回调整体丢弃,因此await nextTick()后没有任何输出; - 再次演示「先忽略上一轮、再写入新值」:
source.value = 'prev'使一次回调入队,ignorePrevAsyncUpdates()将其丢弃,随后source.value = 'after'又产生一次新的更新——最终只打印Changed to after!。
重要限制(引用文档原文,.agents/skills/vueuse-functions/references/watchIgnorable.md):
This feature is only for async flush
'pre'and'post'. Ifflush: 'sync'is used,ignorePrevAsyncUpdates()is a no-op as the watch will trigger immediately after each update to the source. It is still provided for sync flush so the code can be more generic.
- 仅对异步 flush(
'pre'/'post')生效——只有异步 flush 下「更新已发生但回调未执行」的窗口才存在,ignorePrevAsyncUpdates()才有可丢弃的对象; - 使用
flush: 'sync'时它是空操作(no-op),因为每次源更新都会立即同步触发回调,不存在「上一轮未执行的更新」; - API 在 sync flush 下依然保留该属性,目的是让调用方代码可以写成统一的泛型形式,无需按 flush 模式分支判断。
五、完整类型签名与废弃别名
引用文档给出的类型声明(.agents/skills/vueuse-functions/references/watchIgnorable.md)完整继承如下,覆盖单源、多源数组、响应式对象三种重载:
export type IgnoredUpdater = (updater: () => void) => void export type IgnoredPrevAsyncUpdates = () => void export interface WatchIgnorableReturn { ignoreUpdates: IgnoredUpdater ignorePrevAsyncUpdates: IgnoredPrevAsyncUpdates stop: WatchStopHandle } export declare function watchIgnorable< T, Immediate extends Readonly<boolean> = false, >( source: WatchSource<T>, cb: WatchCallback<T, Immediate extends true ? T | undefined : T>, options?: WatchWithFilterOptions<Immediate>, ): WatchIgnorableReturn export declare function watchIgnorable< T extends Readonly<MultiWatchSources>, Immediate extends Readonly<boolean> = false, >( sources: [...T], cb: WatchCallback<MapSources<T>, MapOldSources<T, Immediate>>, options?: WatchWithFilterOptions<Immediate>, ): WatchIgnorableReturn export declare function watchIgnorable< T extends object, Immediate extends Readonly<boolean> = false, >( source: T, cb: WatchCallback<T, Immediate extends true ? T | undefined : T>, options?: WatchWithFilterOptions<Immediate>, ): WatchIgnorableReturn /** @deprecated use `watchIgnorable` instead */ export declare const ignorableWatch: typeof watchIgnorable两点值得注意:
- 返回值
WatchIgnorableReturn固定包含三个成员:ignoreUpdates、ignorePrevAsyncUpdates、stop——即使你只用其中一个,其余句柄也始终可用,便于按 flush 模式或运行期条件灵活选择策略; - 旧命名
ignorableWatch已被标记@deprecated,新项目应统一使用watchIgnorable;从源码结构看,它只是typeof watchIgnorable的别名导出,行为完全等价,仅作兼容保留。
六、与 watchPausable 的边界:何时用「忽略」而非「暂停」
airi 技能库中同时收录了 watchPausable 参考文档。该文档明确标注watchPausable将在未来版本移除,原因是 Vue 3.5 起内置watch已直接返回pause()/resume()(引用文档建议const { stop, pause, resume } = watch(watchSource, callback))。而watchIgnorable没有被 Vue 内置 API 取代——「精确忽略某一次特定更新」的能力仍是 VueUse 独有的。
从源码结构与文档对照看,三者的控制语义边界如下:
| 能力 | 作用对象 | 粒度 | 来源 |
|---|---|---|---|
pause/resume | 整个 watcher | 时间段:暂停后所有更新都不响应,直到恢复 | Vue 3.5+ 内置watch |
ignoreUpdates(fn) | 特定的一次(或一批)写入 | 更新粒度:仅 updater 闭包内的源变更被压制,作用域外变更照常触发 | VueUsewatchIgnorable |
ignorePrevAsyncUpdates() | 已入队但尚未执行的回调 | 单次回调任务:丢弃上一轮已排队的触发(仅flush: 'pre'/'post'生效) | VueUsewatchIgnorable |
典型选择依据:如果需要「一段业务期间完全冻结监听」,用内置pause();如果是「这次写入是我自己做的回写,不想让 watcher 重复反应」,用ignoreUpdates;如果是「异步 flush 下,更新已经发生、回调还没跑,但我想撤销这次通知」,用ignorePrevAsyncUpdates。
七、在 airi 仓库中的使用方式
- 依赖获取:任意已声明
"@vueuse/core": "catalog:"的 workspace 包(版本统一收敛到 pnpm-workspace.yaml 的^14.4.0)中直接import { watchIgnorable } from '@vueuse/core',无需额外安装; - 文档依据:用法、flush 限制与类型签名的权威参考即 .agents/skills/vueuse-functions/references/watchIgnorable.md,其同目录的 SKILL.md 提供全量 Watch 分类函数索引(
watchDebounced、watchWithFilter、whenever等),便于在同一工程内组合选型; - 适用前提:需要 Vue 3 运行环境;
ignorePrevAsyncUpdates的行为依赖flush: 'pre'(默认)或'post',若项目显式配置了flush: 'sync',该句柄为空操作,相关分支逻辑应按此预期编写。
【免费下载链接】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),仅供参考