Airi 开发实践:深入 VueUse `createEventHook` 事件钩子工厂的使用与类型设计

发布时间:2026/11/1 23:13:36

Airi 开发实践:深入 VueUse `createEventHook` 事件钩子工厂的使用与类型设计 Airi 开发实践深入 VueUsecreateEventHook事件钩子工厂的使用与类型设计【免费下载链接】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-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airicreateEventHook是 VueUse 提供的一个轻量级事件钩子event hook工厂函数位于 Utilities工具类分类中用于在不引入事件总线、不依赖组件树的前提下为任意函数/Composable 添加可订阅、可触发、可清理的监听能力。本篇基于 airi 仓库中 VueUse 技能参考文档 createEventHook.md结合其 Type Declarations 与仓库内的配套参考与依赖情况完整讲解该 API 的生产方/消费方两种用法、四类返回方法on/off/trigger/clear的语义、回调类型推导规则以及它在 VueUse 内部如useWebNotification、useConfirmDialog与 Vue 3 单文件组件中的落地方式。读完本篇你将掌握如何为自己的useXxx组合式函数设计一套类型安全、可异步触发、便于解除订阅的钩子接口。createEventHook是什么按照参考文档中的定义createEventHook是Utility for creating event hooks用于创建事件钩子的工具函数。它的典型价值在于把一个未来的事件通知机制封装成独立实例然后将其中的监听 API 暴露给外部调用方。从 SKILL.md 的分类表可以看到createEventHook与useEventBus、useConfirmDialog、useMemoize等同属于 Utilities 工具分类默认调用规则为AUTO——即开发 Vue 3 / Nuxt 3 功能时若需求匹配即可优先选用。与之配合的同类能力函数定位createEventHook为单个自定义 Composable 创建独立的事件钩子实例useConfirmDialogCreates event hooks to support modals and confirmation dialog chains基于事件钩子封装模态框确认链useEventBus全局/具名事件总线属于跨模块广播方案相较事件总线createEventHook的粒度更小、更贴近函数内部状态——它的事件源与事件消费被封装在同一 Composable 的返回接口里不需要全局命名、不需要担心总线事件串扰天然适合作为组件库与业务 Composable 的内部通信原语。核心用法生产方视角参考文档给出的第一个场景是编写一个使用createEventHook的函数生产方/提供方。原文示例以封装fetch为例import { createEventHook } from vueuse/core export function useMyFetch(url) { const fetchResult createEventHookResponse() const fetchError createEventHookany() fetch(url) .then(result fetchResult.trigger(result)) .catch(error fetchError.trigger(error.message)) return { onResult: fetchResult.on, onError: fetchError.on, } }这个例子展示了几个关键设计点每个事件通道独立实例化。这里创建了fetchResult与fetchError两个钩子实例分别承载成功与失败两条通道而不是让监听方自行判断回调参数职责清晰。泛型即载荷类型。createEventHookResponse()表示该钩子触发的载荷是一个Response消费方回调的参数会获得对应的类型提示。只暴露on。生产方把fetchResult.on/fetchError.on放入返回值监听方只能订阅无法在外部随意trigger伪造事件权限边界天然可控。补充一个类型更严谨的封装原示例为了简洁省略了泛型参数的类型标注实际开发中可以为url与触发通道补充完整类型让整个函数对外更可用import { createEventHook } from vueuse/core export interface FetchPayload { ok: boolean status: number data?: unknown } export function useMyFetch(url: string) { const fetchResult createEventHookFetchPayload() const fetchError createEventHook{ message: string; status?: number }() const run async () { try { const res await fetch(url) fetchResult.trigger({ ok: res.ok, status: res.status, data: res.ok ? await res.json() : undefined, }) } catch (error) { fetchError.trigger({ message: (error as Error).message }) } } return { onResult: fetchResult.on, onError: fetchError.on, run, } }这里run让请求可以按需发起而onResult/onError保持与参考文档一致的订阅口。核心用法消费方视角参考文档的第二个场景是在Vue 单文件组件script setup中消费上述函数script setup langts import { useMyFetch } from ./my-fetch-function const { onResult, onError } useMyFetch(my api url) onResult((result) { console.log(result) }) onError((error) { console.error(error) }) /script消费方的体验与 Vue 3 中emits/事件监听的心智模型保持一致onResult(callback)注册成功回调result参数类型即为生产方声明时的Response/FetchPayloadonError(callback)注册失败回调由于生产方只暴露on组件内拿不到trigger无法误触发。不要忽略订阅的返回值on的返回值是一个携带off方法的对象见下方类型声明这意味着订阅是可显式解除的。在需要精确控制生命周期的场景应保存并调用它import { onBeforeUnmount } from vue import { useMyFetch } from ./my-fetch-function const { onResult, onError } useMyFetch(/api/profile) const subscription onResult((result) { console.log(profile updated, result) }) // 组件卸载前解除订阅避免回调泄漏 onBeforeUnmount(() { subscription.off() })需要说明的是与onMounted/onScopeDispose这类自动随作用域清理的 VueUse API 不同createEventHook本身不绑定 Vue 生命周期是否清理、何时清理由使用方自行管理。在组件中按上述方式手动off或调用钩子的clear()是推荐实践。类型声明逐条解读参考文档完整给出了createEventHook的类型声明createEventHook.md逐条拆解如下type CallbackT IsAnyT extends true ? (...param: any) void : [T] extends [void] ? (...param: unknown[]) void : [T] extends [any[]] ? (...param: T) void : (...param: [T, ...unknown[]]) voidCallbackT是一个按载荷类型 T 分派回调签名的条件类型若T是any通过 VueUse 内部类型工具IsAny判断回调接受任意参数(...param: any) void用于保留类型灵活性若T为void表示无载荷事件回调可以接收不定数量的未知参数(...param: unknown[]) void若T本身是元组any[]的元组形态如[string, number]则回调直接展开接收(...param: T) void支持多参数事件其余一般情况回调接收[T, ...unknown[]]——即首个参数为载荷T后续保留未知的扩展参数。export type EventHookOnT any (fn: CallbackT) { off: () void } export type EventHookOffT any (fn: CallbackT) void export type EventHookTriggerT any ( ...param: ParametersCallbackT ) Promiseunknown[] export interface EventHookT any { on: EventHookOnT off: EventHookOffT trigger: EventHookTriggerT clear: () void } export type EventHookReturnT EventHookT四个方法与一个EventHookT接口共同构成了完整钩子能力成员签名要点语义on(fn) { off }注册监听回调返回带off的句柄以便单独解除off(fn) void从监听集合中移除指定回调trigger(...param) Promiseunknown[]触发事件异步执行全部监听并聚合返回每个回调的结果数组clear() void清空所有监听回调值得强调的语义是trigger它是一个异步触发入口返回Promiseunknown[]。这意味着一方面监听回调可以同步执行后由Promise.all风格聚合另一方面调用方可await触发过程。这也解释了为什么trigger的入参类型取ParametersCallbackT——它必须与CallbackT推导出的回调参数签名严格一致。内部实现可以如何还原从类型声明可以推断最朴素的内部实现与Set 存回调 trigger 聚合执行的结构一致。参考文档的注释也说明该实现的灵感源自 vue-apollo 的useEventHook工具。据此还原一个语义等价的最小实现仅用于帮助理解非仓库源码export function createEventHookT any() { const fns: SetCallbackT new Set() const off (fn: CallbackT) { fns.delete(fn) } const on (fn: CallbackT) { fns.add(fn) return { off: () off(fn) } } const trigger (...param: ParametersCallbackT) Promise.all([...fns].map(fn fn(...param))) const clear () { fns.clear() } return { on, off, trigger, clear } }可以看到四个方法对应的底层操作分别是 Set 的add/delete/clear与批量调用整个工具没有任何 DOM、副作用依赖因此在非浏览器环境SSR、测试、Node中也能安全使用——参考文档头部还标注了__NO_SIDE_EFFECTS__便于打包器做 tree-shaking。VueUse 自身如何使用它两个可对照的仓库内参考在 airi 仓库的同一套技能参考集中还能找到createEventHook的两个被使用者是理解该工具定位的最佳佐证。useWebNotification钩子作为返回接口的载体参考 useWebNotification.mdVueUse 的useWebNotificationWeb 通知 API 的响应式封装正是利用createEventHook为外部暴露事件订阅import { useWebNotification } from vueuse/core const { onClick, onShow, onError, onClose } useWebNotification() onClick((evt: Event) { // 处理通知被点击 }) onShow((evt: Event) { // 处理通知展示 }) onError((evt: Event) { // 处理通知出错 }) onClose((evt: Event) { // 处理通知关闭 })在该文档的类型声明中返回值明确写着onClick: EventHookOnEvent、onShow: EventHookOnEvent等——也就是文档所注释的 This composable also utilizes the createEventHook utility。这是 VueUse 官方代码库中用createEventHook为 Composable 增加事件回调的典型范式。useConfirmDialog多个钩子组合成流程状态机同样在 Utilities 分类中useConfirmDialog 被描述为 Creates event hooks to support modals and confirmation dialog chains。它的整体设计思路是把确认弹窗抽象为reveal打开并等待、confirm、cancel等事件通道内部由多个createEventHook实例支撑监听方通过onConfirm/onCancel订阅用户操作结果。可以把它看作用一组事件钩子编排交互状态的进阶示例——当单个通道不够用时按事件语义拆分成多个钩子实例即可组合出更复杂的流程。在 airi 仓库中的落地前提createEventHook属于 VueUse 对外导出的 API参考 createEventHook.md 中import { createEventHook } from vueuse/core的用法。airi 仓库本身通过 pnpm workspace catalog 依赖管理统一引入 VueUse例如 apps/stage-pocket/package.json 中声明了vueuse/core: catalog:在 packages 层packages/stage-ui/package.json、packages/stage-shared/package.json、packages/electron-vueuse/package.json 等均依赖vueuse/core覆盖 Electron 渲染层、Web 应用与共享 UI 包等不同运行环境。因此在 airi 的各 Vue 3 应用与 UI 包中遵循这套技能规范开发自定义 Composable 时createEventHook均可直接按AUTO规则选用当需要一个仅面向当前函数返回接口、可订阅/可触发/可清理的事件通道时优先考虑它而不是自己维护回调数组或引入全局事件总线。实战建议与常见陷阱优先暴露 on、藏起 trigger。参考文档的useMyFetch只返回onResult/onError这是刻意设计的封装边界。只有当调用方确实需要从外部触发时才返回trigger。每个事件通道一个实例。把成功/失败打开/确认/取消等不同语义拆成独立钩子比单一钩子内传联合类型更易读也与useConfirmDialog的多钩子组合风格一致。trigger是异步的。它返回Promiseunknown[]内部等价于对全部监听做批量调用并聚合结果。若某个监听回调抛错需要留意 Promise 的拒绝传播必要时在回调内自行 try/catch。类型先行的载荷设计。利用createEventHookT的泛型载荷为void表示纯信号事件载荷为元组如[id: string, count: number]时回调可展开为多参数载荷为普通对象类型时回调首参即该对象。手动管理清理时机。on返回的{ off }与钩子的clear()是唯一清理途径不会随组件卸载自动触发在script setup里请配合onBeforeUnmount解除订阅避免对已卸载组件产生引用与多余回调。按需聚合多个通道。若觉得回调注册心智负担偏重可先确认需求是否真的需要事件式通知——否则更简单的做法是返回ref/computed状态让消费方 watch这也是本套技能文档中 State 分类各函数如useAsyncState的适用场景。小结createEventHook用极小的 API 面积on/off/trigger/clear为 Vue 3 Composable 补齐了事件通知这一能力维度生产方可以安全地只暴露订阅接口消费方可以获得由泛型推导的强类型回调触发方可以异步批量通知并拿到每个监听的结果。无论你是像useMyFetch那样为异步请求封装结果/错误通知还是像 VueUse 官方在useWebNotification、useConfirmDialog中那样为高阶 Composable 暴露事件式接口都可以直接复用本文梳理的模式与类型约定。仓库内可继续查阅的资料参考文档本体.agents/skills/vueuse-functions/references/createEventHook.mdVueUse 函数分类与调用规则.agents/skills/vueuse-functions/SKILL.mduseWebNotification对EventHookOn的使用.agents/skills/vueuse-functions/references/useWebNotification.mduseConfirmDialog的功能定位.agents/skills/vueuse-functions/SKILL.mdairi 中依赖 VueUse 的示例apps/stage-pocket/package.json、packages/stage-ui/package.json【免费下载链接】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-samas altitude. Capable of realtime voice chat, Minecraft, Factorio playing. Web / macOS / Windows supported.项目地址: https://gitcode.com/GitHub_Trending/ai/airi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
延伸阅读

更多相关文章

2026/11/1 0:01:57

没有AB实验怎么做因果推断?PSM倾向得分匹配实战指南

做AB实验做得久了,你会慢慢意识到一个扎心的事实:不是所有场景都能给你一个干净的随机分组。用户在产品里已经走了一圈,你没法把他们“打回原形”重新抽签。这时候还硬套AB实验的框架,出来的结论不是偏的就是假的,甚至…

2026/11/2 7:05:14

库存管理系统数据流图实战:分层DFD到表结构与审核链路

简介:这份《库存管理系统数据流图》文档面向信息系统分析、数据库课程设计及库存管理流程梳理的学习者,用分层数据流图呈现采购入库、领料出库、库存查询与库存分析预警等业务的数据流转。资源包共1个doc文件,约67KB,内容集中于顶…

2026/11/2 7:05:14

电商高并发架构设计与分布式事务实践

1. 电商架构的演进与核心挑战十年前我刚入行时,电商系统还停留在单体架构阶段。一个War包包含商品、订单、支付所有模块,每次大促前团队通宵扩容服务器的场景历历在目。如今再看行业现状,从淘宝的单元化部署到拼多多的分布式事务方案&#xf…

2026/11/2 7:05:14

CTF隐写术与编码分析实战:以‘鸡你太美‘为例

1. 项目背景解析"[BJDCTF2020]鸡你太美"是一个典型的CTF(夺旗赛)题目名称,这类题目通常会隐藏一些需要解密的线索或漏洞。从题目名称来看,"鸡你太美"是近年来网络流行的一个梗,可能暗示着题目内容与这个梗相关。在CTF比赛…

2026/11/2 7:05:14

VC6集成jsoncpp:编译适配与中文乱码解决

简介:这份面向VC6.0开发者的JSONCPP实战资料包,聚焦老式C工程中JSON读写与中文字符防乱码问题,适合需要在VC6.0环境维护、迁移或复现旧项目的开发者参考。压缩包共72个文件、约3.77MB,以24个头文件、9个cpp源文件和多个inl内联实现…

2026/11/2 7:05:14

VC6 集成 jsoncpp:UTF-8 与 GBK 中文乱码解决实战

简介:这份资源面向仍在维护 Visual C 6.0 老项目的开发者,以及需要在 VC6.0 环境中实现 JSON 读写、又受困于中文字符乱码问题的 C 程序员,提供一套可直接编译运行的 jsoncpp 集成案例。压缩包共 72 个文件、约 3.77MB,以 h 头文件…

2026/11/2 7:00:14

Terraform自动化配置VCFA:从原理到实践

1. 项目概述:Terraform自动化配置VCFA的核心价值在VMware Cloud Foundation(VCF)的部署实践中,VCFA(VMware Cloud Foundation Automation)的配置一直是让运维人员头疼的环节。传统手动配置不仅需要逐步完成…

2026/11/2 6:08:01

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/2 6:39:12

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 1:04:04

工业级SPI隔离设计:硬件抗扰与信号完整性实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 1:04:04

PLFM_RADAR:主动探测数据通道,构建低误报的数据感知系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 1:04:04

工业SPI隔离设计:MAX14431与STM32F746硬件抗干扰实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 6:08:01

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/2 6:39:12

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑