ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Yakit 引擎连接启动屏的渲染端事件总线:用 mitt 构建类型安全信号通信,并用类型系统检测信号源重名

2026/9/16 15:53:41 拓冰建站 浏览量
Yakit 引擎连接启动屏的渲染端事件总线:用 mitt 构建类型安全信号通信,并用类型系统检测信号源重名 Yakit 引擎连接启动屏的渲染端事件总线用 mitt 构建类型安全信号通信并用类型系统检测信号源重名【免费下载链接】yakitCyber Security ALL-IN-ONE Platform项目地址: https://gitcode.com/GitHub_Trending/ya/yakitYakit 的引擎连接启动屏engine-link-startup 渲染端需要让多个组件在不共享直接父子状态引用的前提下传递连接指令这类短生命周期信号。本文以 eventBus 文档 为主体完整讲解该模块的信号源定义规范、注册规则与注意事项并结合 eventBus.ts 的源码剖析其重名信号在编译期报红色波浪线的类型实现原理以及真实信号startAndCreateEngineProcess在启动页与引擎看门狗组件之间的完整调用链。读完后你将掌握在这套体系中新增一个类型安全事件信号的全部操作步骤与底层验证机制。一、模块定位启动屏内的一枚轻量信号总线事件总线位于 app/renderer/engine-link-startup/src/utils/eventBus/ 目录下文档对目录结构给出了明确说明eventBus.ts通信类的定义和 TS 约束events/存放着各个页面的通信源信号源和对应的源里携带内容的类型定义。从源码结构看这套总线基于 mitt 中声明依赖mitt: ^3.0.1并对外只导出一个单例 emitterimport mitt from mitt import type { StartupPageEventProps } from ./events/startupPageEventProps type Events [StartupPageEventProps] type CheckVal ExchangeArrContrastEvents // !!! 该变量声明不能改动 // 如果编辑器(vscode)对该变量报错则说明声明的信号有重名情况请自行检查重名的位置 const checkVal: CheckVal true const emiter mittJoinsEvents() export default emiter注意JoinsEvents把Events数组中所有信号类型做交叉合并T extends [infer H, ...infer U] ? H JoinsU : {}最终作为mitt实例的事件类型参数。这意味着emiter.emit(xxx)、emiter.on(xxx)、emiter.off(xxx)这三个方法都受到编译期类型检查事件名必须是已注册信号源中声明过的 key事件载荷类型也与信号源定义保持一致。二、信号源定义规则文档规定的三条规范文档 index.md 对信号源的落位与命名给出了明确规则存放位置信号源的定义存放在events文件夹内的相关页面文件里。文档特别建议一个页面创建一个 ts 定义文件理由是如果所有信号源统一放到一个 ts 文件里存放量大时影响观感。命名规则信号源的命名规则为${页面名(英文)}EventProps例如启动页的信号类型命名为StartupPageEventProps。引入方式找到eventBus.ts文件中的type Events [...]代码行往数组内依次加入声明好的信号源的定义名。如果引入后下列代码出现红色波浪线报错let checkVal: CheckVal true则说明引入的信号源存在同名重复问题需要检查并调整名称。当前仓库中已存在的真实信号源定义可参见 events/startupPageEventProps.tsexport type StartupPageEventProps { /** 启动并创建引擎进程 */ startAndCreateEngineProcess?: boolean }这里的事件 payload 被定义为选填?:且Events数组中注册的正是该类型名StartupPageEventProps与文档第 1、2 条规则一一对应。三、重名检测的类型实现checkVal 为什么能变红文档只说出现红色波浪线说明信号源重名其背后是一组递归条件类型。逐层拆解 eventBus.tstype ContrastT extends object, E extends object [keyof T keyof E] extends [never] ? never : string type OneToArrT extends object, E extends object[] E extends [infer X extends object, ...infer Y extends object[]] ? [ContrastT, X] extends [never] ? OneToArrT, Y : string : number type ArrContrastE extends object[] E extends [infer X extends object, ...infer Y extends object[]] ? OneToArrX, Y extends number ? ArrContrastY : string : number type ExchangeT T extends number ? boolean : never type JoinsT extends object[] T extends [infer H extends object, ...infer U extends object[]] ? H JoinsU : {}ContrastT, E判断两个信号类型是否有共同的事件名 key。有交集返回string冲突标记无交集返回never。这里用[keyof T keyof E] extends [never]的元组包裹写法是为了规避联合类型的分配行为保证整个 key 集合求交后再判断。OneToArrT, E[]固定第一个信号类型T遍历数组中其余每一个信号类型。只要发现任何一个与T有同名 key立即返回string遍历完都没有冲突则返回number。ArrContrastE[]外层递归——取数组首元素用OneToArr检查它与其余所有元素是否都无重名。全部通过则继续检查数组剩余部分最终返回number任何一处重名都会得到string。ExchangeT把无冲突的结果number映射为boolean有冲突的结果string映射为never。于是type CheckVal ExchangeArrContrastEvents const checkVal: CheckVal true所有信号源无重名时CheckVal booleantrue赋值合法代码无报错任一信号源出现同名事件时CheckVal never而true不能赋给neverVSCode 中该变量声明行立刻出现红色波浪线。文档标注该变量声明不能改动正是因为它充当了一个零运行成本的编译期哨兵不需要测试用例、不需要 lint 规则仅靠一个恒等赋值就能在开发者引入新信号源的瞬间发现命名冲突。四、实战链路startAndCreateEngineProcess 从发射到消费这条信号目前是仓库中唯一的真实事件源完整链路恰好覆盖发送方—总线—接收方三个环节可作为新增信号时的参照模板。1. 发送方启动页主组件在 StartupPage/index.tsx 中onStartLinkEngine负责在本地或远程模式拿到连接凭证后向总线发射信号// 开始连接引擎 const onStartLinkEngine useMemoizedFn(() { isStopSend.current false setTimeout(() { emiter.emit(startAndCreateEngineProcess) }, 100) })从源码结构看emit被包裹在 100ms 的setTimeout中且该函数同时被handleLinkRemoteEngine远程连接与handleLinkLocalEngine本地连接两条路径复用——发射信号时并未附带 payload这也呼应了文档如不附加值则建议 TS 定义为选填的规范因此信号定义才写成了startAndCreateEngineProcess?: boolean。2. 接收方引擎看门狗组件YaklangEngineWatchDog/index.tsx 在useEffect中订阅该信号并在清理函数中解除监听/** 接受连接引擎的指令 */ useEffect(() { emiter.on(startAndCreateEngineProcess, () { engineTest() }) return () { emiter.off(startAndCreateEngineProcess) } }, [])回调engineTest会读取credential连接模式与端口先通过yakitEngine.connectYaklangEngine探测目标引擎进程是否存活若失败且mode local则置位autoStartProgress由防抖的useDebounceEffectwait: 1000触发grpcStartLocalEngine启动本地引擎进程远程模式失败则只提示错误、不自动拉起进程。这条发射信号 → 认证引擎 → 按需自动启动 → 开启 3 秒周期探活的完整逻辑正是这枚信号存在的业务目的。3. 测试中的事件总线单元测试 YaklangEngineWatchDog/test/index.test.tsx 采用 mock 方式隔离总线验证了事件触发 → 回调执行这一契约vi.mock(/utils/eventBus/eventBus, () ({ default: { on: vi.fn(), off: vi.fn(), }, })) // ... vi.mocked(emiter.on).mockImplementation((event: any, callback) { if (event startAndCreateEngineProcess) { triggerEngineTest callback as () void } return emiter })测试用例覆盖了Mode为空不发起连接、Port 0不发起连接、连接成功触发onKeepaliveShouldChange(true)、本地模式失败后自动调用grpcStartLocalEngine且版本经toEngineHandshakeName映射、远程模式不自动启动等分支为信号消费侧的行为提供了可回归验证的依据。五、文档注意事项逐条解读文档注意事项一节给出的三条约束结合源码可以进一步理解其动机不要在 map 方法内的组件设置事件监听。启动屏内存在大量列表渲染场景如果为 map 生成的每个子项组件都注册emiter.on同一事件会在多个同构实例上重复触发而信号本身无法区分发给哪一个页面/实例。文档明确把区分同事件监听的责任交给使用者如果需要设置请自行解决如何区别不同页面同事件监听的问题。当前仓库中YaklangEngineWatchDog这类单例挂载的组件正是符合规范的做法on与off成对出现于同一个useEffect避免监听泄漏。页面内事件信号的发送值如不附加值则建议 TS 定义为选填。startAndCreateEngineProcess?: boolean就是范例——emiter.emit(startAndCreateEngineProcess)不带 payload 依然通过类型检查。首选类型建议为 string。文档给出的原因是复杂的类型可能导致各页面信号定义交叉类型时出现 never 类型。对照实现可以理解这一点JoinsEvents会将所有信号类型做交叉。当两个信号源恰好存在同名 key 且类型不兼容时交叉结果会坍缩为never即便重名被checkVal的红色波浪线拦截复杂嵌套类型对象、数组、联合也会增加交叉推导出错面的概率。使用string、boolean这类基础类型可最大程度规避该问题。六、在项目中新增一个类型安全事件信号的完整步骤综合文档规则与源码实现新增信号的完整操作路径为在 events/ 目录下为对应页面新建一个定义文件沿用一页面一文件约定导出形如XxxPageEventProps的类型字段为事件名载荷为基础类型并视情况设为选填回到 eventBus.tsimport type引入新类型并将其加入type Events [...]数组观察const checkVal: CheckVal true一行——无红色波浪线即说明命名无冲突可以提交出现波浪线则回到第 1 步排查重名 key发送侧用emiter.emit(事件名, payload)接收侧在useEffect中emiter.on并在 cleanup 中emiter.off成对书写。路径说明工程通过 vite.config.ts 配置了: path.resolve(rootDir, src)的别名因此业务代码中统一以/utils/eventBus/eventBus导入该单例例如启动页的import emiter from /utils/eventBus/eventBus。七、适用前提与边界该事件总线属于engine-link-startup 启动屏渲染端内部的通信机制服务于启动页—引擎看门狗这类同页面内的组件解耦跨窗口、跨渲染端的通信走的是 Electron 主进程桥如 electronBridge.ts 中的yakitEngine/yakitApp不要与本文的 mitt 总线混淆。重名检测依赖 TypeScript 编译器与编辑器行为checkVal报错是编译期现象Joins交叉类型带来的类型收窄质量也随信号类型复杂度下降这正是文档建议载荷首选string的底层原因。文中涉及的行为均以当前仓库代码为准mitt ^3.0.1依赖、Events [StartupPageEventProps]的注册方式、startAndCreateEngineProcess信号链路及其测试用例均可在对应文件中直接复核。【免费下载链接】yakitCyber Security ALL-IN-ONE Platform项目地址: https://gitcode.com/GitHub_Trending/ya/yakit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考