- 人工智能
- AI Agent
- MCP 服务
- 工具调用
- GUI 自动化
- 语音
- 数字人
- 交互助手
【免费下载链接】airi
💖🧸 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器,目标是接近 Neuro-sama 的高度;支持实时语音聊天、Minecraft 和 Factorio 游玩,支持 Web / macOS / Windows。
本篇技术指南以 airi 仓库中vueuse-functions技能包 的useThrottledRefHistory参考文档 为骨架,讲解 VueUse 中这个「带节流过滤的useRefHistory简写」组合式函数:它如何在一个 ref 的值高频变化时,按节流节奏沉淀历史快照,并顺带提供undo/redo能力。读完本文,你将掌握它的参数语义、与useDebouncedRefHistory/useManualRefHistory的取舍,以及如何在类似 airi 这类 Vue 3 应用中为编辑器、设置表单等场景落地可撤销的本地状态。
函数定位:State 分类下的节流版 ref 历史记录
useThrottledRefHistory属于 VueUse 的 State 分类。在 airi 仓库的 SKILL.md 函数索引表 中,它的定位是:
Shorthand for
useRefHistorywith throttled filter(带节流过滤器的useRefHistory简写),调用规则为AUTO(在适用场景下自动使用)。
这意味着它是 VueUse 三大「ref 历史记录」函数之一,三者共享同一套返回结构与大部分选项:
| 函数 | 过滤方式 | 快照时机 |
|---|---|---|
useRefHistory | 无过滤,基于watch触发 | 每个 tick 内异步批量提交 |
useThrottledRefHistory | throttleFilter(节流) | 节流窗口内最多按前/后沿取样 |
useDebouncedRefHistory | debounceFilter(防抖) | 值停止变化指定时间后才取样 |
三者的类型定义都基于useRefHistory:UseThrottledRefHistoryReturn直接等价于UseRefHistoryReturn<Raw, Serialized>,区别只在于把原本的eventFilter选项替换成了throttle与trailing两个专有选项。这也是它名字中 "Shorthand" 的含义——内部本质上就是把useRefHistory挂上一个节流事件过滤器。
快速上手:为计数器接入节流历史
文档给出的核心用法如下,注意这里显式传入deep: true与throttle: 1000:
import { useThrottledRefHistory } from '@vueuse/core' import { shallowRef } from 'vue' const counter = shallowRef(0) const { history, undo, redo } = useThrottledRefHistory(counter, { deep: true, throttle: 1000 })行为上,这个函数在计数器值被修改后的瞬间立刻采集第一份快照,之后在 1000ms 的节流窗口内不再重复记录,直到窗口结束后(若期间仍有变更)再采集第二份快照。也就是说,它同时具备「前沿立即生效 + 后沿兜底捕获」的节流语义,避免高频写入把 history 撑爆,同时保证最终状态不会丢失。
history是一个响应式数组,最新快照在数组头部(history[0]为当前值,越靠后越旧);调用undo()会把 ref 回退到上一份快照,redo()则反向重做。默认情况下历史容量不设上限,可用clear()显式清空。
参数详解:UseThrottledRefHistoryOptions全解
文档给出的类型声明中,选项类型是UseRefHistoryOptions去掉eventFilter后,再并入节流专属参数:
export type UseThrottledRefHistoryOptions<Raw, Serialized = Raw> = Omit< UseRefHistoryOptions<Raw, Serialized>, "eventFilter" > & { throttle?: MaybeRef<number> trailing?: boolean } export type UseThrottledRefHistoryReturn< Raw, Serialized = Raw, > = UseRefHistoryReturn<Raw, Serialized>节流专属参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
throttle | MaybeRef<number> | — | 节流间隔(毫秒)。MaybeRef意味着可以传一个 ref,间隔本身也能是响应式的;传 0 时退化为每次变更都记录。 |
trailing | boolean | true | 是否在节流窗口结束时捕获尾随快照。关闭后只保留节流窗口开头的快照,末尾的最终状态不会被单独记录。 |
从useRefHistory继承的通用选项
throttle/trailing之外,其余选项与 useRefHistory 完整参考 完全一致,同样适用于本函数:
deep?: boolean(默认false):对象/数组场景下,直接修改其属性不会改变引用,不会触发提交;传入true才会深度监听并为每份快照创建克隆。文档示例中deep: true正是为计数器这种基本类型之外的场景准备的保险写法。capacity?: number:最多保留的历史条数,默认无限;超出后旧记录被丢弃。clone?: boolean | CloneFn<Raw>:快照时克隆值。默认仅内嵌JSON.parse(JSON.stringify(x))这种最简克隆;可换成structuredClone或cloneDeep等完整克隆函数。deep: true时相当于自动启用克隆。dump?: (v: Raw) => Serialized/parse?: (v: Serialized) => Raw:自定义序列化与反序列化。例如dump: JSON.stringify, parse: JSON.parse,可以让快照直接以字符串形态存入localStorage,并在撤销时省去一次克隆。shouldCommit?: (oldValue, newValue) => boolean:每次变更提交前的裁决函数,返回false则跳过本次记录,可用于按值变化量过滤无意义的历史点。
关于flush
useRefHistory系列默认沿用 Vue watcher 的'pre'flush 时序,同一 tick 内的多次修改会被合并成一条历史记录;若需要在同一 tick 内产生多条记录,可改用commit()手动提交。节流版本同样继承这一行为,意味着节流与异步批处理是叠加生效的——既要等待 Vue 的批量刷新,又要受节流窗口约束。
trailing的行为语义与底层原理
trailing之所以单独出现在选项里,是因为节流过滤器(throttle)天然包含「前缘(leading)/ 后缘(trailing)」两个触发边。可以参考同技能包中 watchThrottled 的选项说明 来理解:
| 组合 | 效果 |
|---|---|
leading: true, trailing: false | 只在节流周期开始触发一次,高频期间的中间值全部丢弃 |
leading: false, trailing: true | 只在节流周期结束时记录最终值 |
leading: true, trailing: true(VueUse 默认) | 周期开始立即记录,结束后再补一条最终值 |
在useThrottledRefHistory中,trailing是唯一暴露出来的边控制项,前缘默认开启(即「值一变就立刻记录第一份快照」)。这与仓库文档描述的「takes the first snapshot right after the counter's value was changed and the second with a delay of 1000ms」完全吻合:第一条来自前缘、第二条来自后缘。
从原理上推断(参见同包 refThrottled 参考 与watchThrottled的 "How It Works" 一节),节流函数本质上等价于:
watchWithFilter(source, commit, { eventFilter: throttleFilter(1000), })即通过eventFilter管线把 Vuewatch的每次触发交给throttleFilter裁决,从而在底层复用同一套过滤器基础设施。
节流 vs 防抖 vs 手动提交:怎么选
airi 技能包把三个「历史记录」函数放在同一分类下(State 分类索引),实际选型建议:
- 连续高频、但需要保留过程中间态(如拖拽滑块、画布移动、实时参数调节):用
useThrottledRefHistory,节流确保历史点密度可控,前缘快照保证「拖到哪都能回退到最近的稳定点」。 - 只关心「停下来之后」的结果(如输入框停止输入后再记录):用
useDebouncedRefHistory(参考文档),它在值开始变化 1000ms 后才取样,中间态一律不记录,历史更干净。 - 完全掌控提交时机:用
useManualRefHistory,仅在业务逻辑认为「这一步是完整操作」时调用commit(),例如一次批量修改只产生一条历史点。 - 无节流、全量记录:直接用
useRefHistory。
airi 仓库中的实际应用与源码证据
airi 是一个大量使用@vueuse/core的 Vue 3 全栈 monorepo。在根目录 pnpm-workspace.yaml 中,catalog 统一声明了'@vueuse/core': ^14.4.0(以及@vueuse/shared、@vueuse/motion),各子应用与包(如 apps/stage-tamagotchi/package.json、apps/stage-web/package.json、packages/electron-vueuse/package.json)均通过catalog:引用,说明 VueUse 生态是该项目响应式基础设施的一部分。
仓库中与本主题最直接相关的实战案例是 Live2D 关键帧编辑器keyframe-editor.vue:
// keyframe-editor.vue (L65-L68) const { canRedo, canUndo, clear, commit, redo, undo } = useManualRefHistory(project, { clone: structuredClone, capacity: 100, })这里用structuredClone对 Live2D 运动工程对象做完整克隆、用capacity: 100限制历史深度,并通过updateProject/replaceProject在每次增删关键帧、移动时间点后显式commit()——这正是上一节「手动提交」选型的现实写照。它验证了useRefHistory系列返回对象在真实编辑器中的完整用法(canUndo/canRedo驱动按钮禁用态,clear用于清空时间线)。如果你需要的是「拖拽关键帧期间自动按节流记录」,把这里的useManualRefHistory换成useThrottledRefHistory(project, { clone: structuredClone, throttle: 200 })即可得到同样的撤销/重做界面,但省去手动commit()调用。
另一个证据位于语音提供方设置页openai-compatible-audio-speech.vue(第 67 行),源码中留有注释:
// TODO: use `useRefHistory` for this它正在用一段手写的watch+ 手工同步逻辑实现「provider 配置被外部重置时回写本地 ref」的复位功能。这恰恰说明:这类「记录变更前状态、支持回退/重置」的需求,正是useRefHistory/useThrottledRefHistory家族的典型用武之地——当高频设置项需要可撤销历史或重置快照时,一行组合式函数即可替代手写 watcher。
完整类型声明
最后给出文档收录的完整函数签名(去掉对外部文档站的跳转,本文以仓库内参考文档为准):
export type UseThrottledRefHistoryOptions<Raw, Serialized = Raw> = Omit< UseRefHistoryOptions<Raw, Serialized>, "eventFilter" > & { throttle?: MaybeRef<number> trailing?: boolean } export type UseThrottledRefHistoryReturn< Raw, Serialized = Raw, > = UseRefHistoryReturn<Raw, Serialized> export declare function useThrottledRefHistory<Raw, Serialized = Raw>( source: Ref<Raw>, options?: UseThrottledRefHistoryOptions<Raw, Serialized>, ): UseThrottledRefHistoryReturn<Raw, Serialized>其中返回对象UseRefHistoryReturn除history、undo、redo外,还包含canUndo、canRedo、clear()、commit()、batch(fn)、pause()、resume(commit?)、isTracking与dispose(),完整语义见 useRefHistory 参考文档。综上,useThrottledRefHistory的核心价值在于:以最小的心智成本,为高频变化的响应式状态提供「有节流、可撤销、可重做」的历史能力——在 airi 这类需要实时调节(Live2D 动作、语音参数、画布元素)的 Vue 3 应用中,它比手写 watcher + 快照数组的方案更简洁、更不易出错。
- 人工智能
- AI Agent
- MCP 服务
- 工具调用
- GUI 自动化
- 语音
- 数字人
- 交互助手
【免费下载链接】airi
💖🧸 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器,目标是接近 Neuro-sama 的高度;支持实时语音聊天、Minecraft 和 Factorio 游玩,支持 Web / macOS / Windows。
相关推荐
Airi 项目中的 VueUse useThrottledRefHistory:为 ref 状态机接入节流历史记录与撤销重做
Airi 项目中的 VueUse useThrottledRefHistory:为 ref 状态机接入节流历史记录与撤销重做 useThrottledRefHi
人工智能AI 应用大模型AI Agent交互助手语音数字人后端前端桌面应用移动开发GUI 自动化MCP 服务VueUse useThrottledRefHistory 深度指南:为 Ref 状态开启节流式历史追踪与撤销重做
VueUse useThrottledRefHistory 深度指南:为 Ref 状态开启节流式历史追踪与撤销重做 导读 useThrottledRefHist
前端Airi 项目中的 useRefHistory 实战:用 VueUse 为 ref 状态构建撤销 / 重做历史
Airi 项目中的 useRefHistory 实战:用 VueUse 为 ref 状态构建撤销 / 重做历史 useRefHistory 是 VueUse 在
人工智能AI AgentMCP 服务工具调用GUI 自动化语音数字人交互助手后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考