Wails2 系列 07:Wails Runtime:窗口、事件、剪贴板和通知的统一封装
cbowen

适合读者

适合已经使用过 Wails runtime,并在项目中处理窗口、事件、通知、剪贴板或进度推送的开发者。

问题场景

Wails runtime 提供了很多桌面能力:窗口显示隐藏、系统通知、事件通信、剪贴板、文件对话框、浏览器打开链接。能力越方便,越容易被随手调用。

结果是 Go 服务里到处 emit 事件,前端页面到处监听事件,窗口操作散落在组件里。某个事件是谁发的、谁收的、什么时候取消监听,开始变得不明显。

项目观察

当前项目中可以观察到两类 runtime 使用:

  • Go 侧服务通过 runtime 发送通知、控制窗口、发出事件。
  • 前端在 frontend/src/services/runtime 中封装窗口、事件、剪贴板等能力。

这种拆法说明 runtime 不只是工具函数,而是跨层通信和系统能力的入口。

拆分原则

第一,区分 Go 侧 runtime 和前端 runtime。

Go 侧更适合处理系统能力、文件对话框、通知、窗口生命周期、后台任务进度推送。前端侧更适合处理用户交互触发的窗口操作、剪贴板、事件订阅。

第二,事件名要稳定集中。

不要在多个文件里手写不同字符串。事件名最好有统一常量或封装函数。

第三,监听和取消监听要成对出现。

前端页面订阅事件时,组件卸载必须清理监听,否则页面切换后可能重复响应。

改造示例

不要在页面里到处直接写 runtime 调用:

1
2
3
4
5
import { EventsOn, EventsOff } from "../../wailsjs/runtime"

EventsOn("download-progress", (progress) => {
// update UI
})

可以先做事件封装:

1
2
3
4
5
6
7
8
9
10
import { EventsOn, EventsOff } from "../../wailsjs/runtime"

export const RuntimeEvents = {
UpdateProgress: "update-progress",
} as const

export function onUpdateProgress(callback: (value: number) => void) {
EventsOn(RuntimeEvents.UpdateProgress, callback)
return () => EventsOff(RuntimeEvents.UpdateProgress)
}

页面中使用时就更清楚:

1
2
3
4
5
useEffect(() => {
return onUpdateProgress((value) => {
setProgress(value)
})
}, [])

Go 侧发事件也可以集中命名:

1
2
3
4
5
const EventUpdateProgress = "update-progress"

func (s *UpdateService) emitProgress(value int) {
runtime.EventsEmit(s.ctx, EventUpdateProgress, value)
}

这样前后端之间的契约就不再靠散落字符串维持。

检查清单

  • runtime 调用是否集中在少数封装文件或服务中?
  • 事件名是否有统一定义?
  • 前端事件监听是否在组件卸载时清理?
  • 进度推送、流式输出等场景是否有明确事件契约?
  • 窗口、剪贴板、通知是否避免散落在普通 UI 组件里?
  • Go 服务是否只在需要系统能力时持有 Wails context?
  • 调试事件流时,是否能快速找到发送方和接收方?

下一篇

下一篇会讨论跨平台能力。Wails 项目一旦要照顾 macOS、Windows 和 Linux,就需要把平台差异从业务逻辑里剥离出来。

 评论
评论插件加载失败
正在加载评论插件