尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

go-app 框架 Action 机制全解析:用 Go 语言在 PWA 中构建跨组件自定义事件系统

go-app 框架 Action 机制全解析:用 Go 语言在 PWA 中构建跨组件自定义事件系统 前端Web框架WebAssembly【免费下载链接】go-appA package to build progressive web apps with Go programming language and WebAssembly.项目地址https://gitcode.com/gh_mirrors/go/go-app点击查看免费下载导读Action 是 go-app用 Go 与 WebAssembly 构建渐进式 Web 应用的开源框架中一套轻量、解耦的自定义事件机制它由任意组件通过Context创建随后在整个应用中传播既可以在独立 goroutine 上被全局处理器异步消费也可以被任意组件在 UI 线程上同步接收。读完本文你将掌握 Action 的创建含携带 payload 与附加标签、全局级与组件级两种处理方式、底层分发原理actionManager与 goroutine 调度以及如何用它把业务逻辑从 UI 中剥离、在组件之间传递数据。什么是 ActionAction 本质上是一个在应用中传播的自定义事件其定义来自 pkg/app/action.gotype Action struct { // Name uniquely identifies the action. Name string // Value is the data associated with the action and can be nil. Value any // Tags provide additional context or metadata for the action. Tags Tags }它由三个字段组成字段类型含义Namestring唯一标识 Action 的名称也是处理器注册与匹配的依据Valueany随 Action 携带的数据可为nilTagsTags即map[string]string附加的上下文信息类似于 HTTP 请求头header之于请求Action 的传播不受组件层级限制任何组件都能创建它任何已注册的处理器无论是全局的还是某个组件的都能接收它。这种创建—传播—消费的模型让组件之间不需要互相持有引用即可通信。创建 ActionAction 只能通过 Context 创建框架在 pkg/app/context.go 中提供了两个方法// NewAction generates a new action for handling. func (ctx Context) NewAction(action string, tags ...Tagger) { ctx.NewActionWithValue(action, nil, tags...) } // NewActionWithValue crafts an action with a given value for processing. func (ctx Context) NewActionWithValue(action string, v any, tags ...Tagger) { var tagMap Tags for _, tag : range tags { if tagMap nil { tagMap make(Tags) } for k, v : range tag.Tags() { tagMap[k] v } } ctx.postAction(ctx, Action{ Name: action, Value: v, Tags: tagMap, }) }从源码可以看到NewAction只是NewActionWithValue的便捷封装payload 传nil而NewActionWithValue会把传入的所有Tagger的标签合并进一个Tagsmap再通过内部postAction完成分发。基础用法只带名称func (h *hello) onInputChange(ctx app.Context, e app.Event) { ctx.NewAction(greet) }携带 payload创建 Action 时可以附加一个任意类型的值payload在消费端通过类型断言取出func (h *hello) onInputChange(ctx app.Context, e app.Event) { name : ctx.JSSrc().Get(value).String() ctx.NewActionWithValue(greet, name) }附加标签Tags标签的作用类似 HTTP header为 Action 附加额外的上下文信息但不属于业务数据主体。通过app.T构造键值对标签func (h *hello) onInputChange(ctx app.Context, e app.Event) { name : ctx.JSSrc().Get(value).String() ctx.NewActionWithValue(greet, name, app.T(source, input), app.T(event, change), ) }app.T的实现在 pkg/app/tag.go注意其内部会调用toString把任意值转换为字符串func T(name string, value any) Tag { return Tag{ Name: name, Value: toString(value), } }Tags还提供了Set(name string, v any)写入并转字符串与Get(name string) string读取两个便捷方法pkg/app/tag.go消费端可以这样读取func (h *hello) handleGreet(ctx app.Context, a app.Action) { src : a.Tags.Get(source) // input }处理 ActionAction 被创建后会在整个应用中传播。处理它的回调签名统一为ActionHandlerpkg/app/action.gotype ActionHandler func(Context, Action)处理方式分两个层级全局级与组件级。全局级处理在独立 goroutine 上集中业务逻辑全局级处理通过app.Handle注册处理器在独立的 goroutine 上异步执行典型场景是把业务逻辑从 UI 中集中、抽离出来func main() { app.Handle(greet, handleGreet) // Registering action handler. app.Route(/, hello{}) app.RunWhenOnBrowser() // ... } // Action handler that is called on a separate goroutine when a greet action // is created. func handleGreet(ctx app.Context, a app.Action) { name, ok : a.Value.(string) // Checks if a name was given. if !ok { fmt.Println(Hello, World) return } fmt.Println(Hello,, name) }这里的几个要点app.Handle只接受 Action 名称与处理器两个参数pkg/app/action.go处理器保存在全局actionHandlersmap 中引擎启动时会被注入到asynchronousActionHandlers字段pkg/app/engine.go专供异步执行处理器运行在独立的 goroutine上不阻塞 UI 渲染因此适合日志上报、数据请求、业务编排等耗时或非 UI 型逻辑由于不在 UI goroutine 上处理器中不应该直接修改组件字段或 DOM如需回到 UI 线程应使用ctx.Dispatch/ctx.Update等机制。组件级处理在 UI goroutine 上传递数据组件级处理通过Context.Handle注册处理器在 UI goroutine 上执行是把一个组件的数据发给另一个组件的首选方式。典型实现是在组件挂载OnMount时注册处理器func (h *hello) OnMount(ctx app.Context) { ctx.Handle(greet, h.handleGreet) // Registering action handler. } func (h *hello) onChange(ctx app.Context, e app.Event) { name : ctx.JSSrc().Get(value).String() ctx.NewActionWithValue(greet, name) // Creating greet action. } // Action handler that is called on the UI goroutine when a greet action is // created. func (h *hello) handleGreet(ctx app.Context, a app.Action) { name, ok : a.Value.(string) // Checks if a name was given. if !ok { return } h.name name }组件级处理的关键差异ctx.Handle在 pkg/app/context.go 中把处理器绑定到当前组件source并以asyncfalse注册意味着走 UI goroutine因为运行在 UI goroutine 上处理器里可以直接修改组件字段如上面的h.name name并触发组件重绘。全局级 vs 组件级如何选择维度全局级app.Handle组件级ctx.Handle注册方式app.Handle(name, handler)ctx.Handle(name, handler)执行线程独立 goroutine异步UI goroutine同步分发主要用途集中、抽离业务逻辑组件间传递数据能否直接改组件状态不适合不在 UI 线程可以自动触发重绘源码视角Action 是如何被分发的组件级 Action 的分发由actionManager完成其实现位于 pkg/app/action.go。核心逻辑在Post方法中func (m *actionManager) Post(ctx Context, a Action) { m.mutex.Lock() defer m.mutex.Unlock() for key, handler : range m.handlers[a.Name] { source : handler.Source if !source.Mounted() { delete(m.handlers[a.Name], key) continue } ctx.sourceElement source function : handler.Function if handler.Async { ctx.Async(func() { function(ctx, a) }) continue } ctx.Dispatch(func(ctx Context) { function(ctx, a) }) } }这段代码揭示了几个值得注意的实现细节按 Action 名称查找处理器内部handlers是map[string]map[string]actionHandler第一层 key 是 Action 名称第二层 key 由actionHandlerKey生成/类型/指针/指针确保同一组件同一处理器不会被重复注册。自动清理失效处理器分发时如果发现注册该处理器的组件已经卸载!source.Mounted()会立即从 map 中删除该处理器防止内存泄漏与幽灵回调Cleanup方法则做整体清理pkg/app/action.go。两条执行路径Async路径handler.Async true调用ctx.Async在独立 goroutine 上执行——对应全局app.Handle注册的处理器Dispatch路径调用ctx.Dispatch回到 UI goroutine 执行——对应ctx.Handle注册的组件级处理器。Dispatch在 pkg/app/context.go 中实现执行回调前会先对源组件及其祖先链调用addComponentUpdate做更新标记因此组件级处理器修改字段后 UI 会自动刷新。Context.Handle与引擎的接线在 pkg/app/engine.go 中完成handleAction: e.actions.Handle, postAction: e.actions.Post,即组件级 Action 的注册与分发最终都收敛到同一个actionManager。测试验证Action 的行为约定仓库的单元测试把上述行为固化成了可验证的约定可作为阅读与二次开发时的行为参考pkg/app/action_test.go 分别验证了异步调用asynctrue走 goroutine与同步调用asyncfalse走 Dispatch两条路径下处理器都会被正确触发pkg/app/action_test.go 验证了当 Action 分发给已卸载的组件时处理器不会被调用且会从注册表移除pkg/app/action_test.go 验证了Cleanup会清空所有已卸载组件注册的处理器pkg/app/context_test.go 端到端验证了ctx.Handlectx.NewActionWithValue的完整链路Action 的Name、Value此处为整数21以及通过T与Tags混传的标签hello→world、foo→bar都能被正确接收同时验证ctx.NewAction创建的 Action 其Value与Tags均为nil。这些测试同时佐证了一个重要细节NewActionWithValue的标签参数是...Tagger因此可以同时混传app.T(...)标签与app.Tags{...}map。实践建议与常见误区综合文档与源码编写 Action 相关代码时有几点值得留意命名要有全局视野Action 名称是全局匹配的字符串建议采用能表达业务语义的命名如greet、cart/add-item避免与内置组件事件命名冲突。区分线程边界全局处理器在 goroutine 上运行不要在里边直接改组件字段组件级处理器在 UI goroutine 上运行不要在里面执行耗时的阻塞操作耗时逻辑应委托给全局处理器或ctx.Async。善用 payload 与 Tags 的边界Value承载业务数据主体Tags承载上下文元数据来源、触发事件等二者分离让消费端代码意图更清晰。组件卸载即失效组件级处理器绑定的是注册时的组件实例组件卸载后处理器会被自动清理无需手动反注册。下一步Action 是 go-app 组件通信体系中的一环与之配套的状态管理机制可以查看 状态管理文档完整的Action、ActionHandler、Context.Handle、Context.NewAction、Context.NewActionWithValueAPI 签名可查阅 参考手册。赞分享前端Web框架WebAssembly【免费下载链接】go-appA package to build progressive web apps with Go programming language and WebAssembly.项目地址https://gitcode.com/gh_mirrors/go/go-app点击查看免费下载相关推荐Go-App终极指南用Go语言和WebAssembly构建跨平台PWA应用Go App终极指南用Go语言和WebAssembly构建跨平台PWA应用 go app是一个强大的Go语言包专门用于构建 渐进式Web应用PWA 和前端Web框架WebAssemblyGo语言插件系统终极指南动态扩展机制全解析与实战应用Go语言插件系统终极指南动态扩展机制全解析与实战应用 Go语言插件系统为应用程序提供了强大的动态扩展能力让开发者能够在运行时加载和执行外部模块。这种机制使得编程语言编译器语言运行时标准库并发编程终极指南5分钟掌握Sketch设计稿转HTML的Marketch插件完整教程终极指南5分钟掌握Sketch设计稿转HTML的Marketch插件完整教程 还在为Sketch设计稿到HTML代码的手动转换而烦恼吗Marketch插件为开发工具前端上一篇mini-rv32ima 核心技术揭秘如何实现无依赖的 C 语言仿真器下一篇Jaaz终极指南5分钟掌握开源AI创意助手的完整部署方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表