简介
go-app是一个使用 Go + WebAssembly 技术编写渐进式 Web 应用的库 。WebAssembly 是一种可以运行在现代浏览器中的新式代码 。近两年来,WebAssembly 技术取得了较大的发展 。我们现在已经可以使用 C/C++/Rust/Go 等高级语言编写 WebAssembly 代码 。本来就来介绍go-app这个可以方便地使用 Go 语言来编写 WebAssembly 代码的库 。
快速使用
go-app对 Go 语言版本有较高的要求(Go 1.14+),而且必须使用Go module 。先创建一个目录并初始化Go Module(Win10 + Git Bash):
nbsp;mkdir go-app && cd go-appnbsp;go mod init
然后下载安装go-app包:
nbsp;go get -u -v github.com/maxence-charriere/go-app/v6
至于Go module的详细使用,去看煎鱼大佬的Go Modules 终极入门 。
首先,我们要编写 WebAssembly 程序:
package mainimport "github.com/maxence-charriere/go-app/v6/pkg/app"type Greeting struct {app.Componame string}func (g *Greeting) Render() app.UI {return app.Div().Body(app.Main().Body(app.H1().Body(app.Text("Hello, "),app.If(g.name != "",app.Text(g.name),).Else(app.Text("World"),),),),app.Input().Value(g.name).Placeholder("What is your name?").AutoFocus(true).OnChange(g.OnInputChange),)}func (g *Greeting) OnInputChange(src app.Value, e app.Event) {g.name = src.Get("value").String()g.Update()}func main() {app.Route("/", &Greeting{})app.Run()}
在go-app中使用组件来划分功能模块,每个组件结构中必须内嵌app.Compo 。组件要实现Render()方法 , 在需要显示该组件时会调用此方法返回显示的页面 。go-app使用声明式语法,完全使用 Go 就可以编写 HTML 页面 , 上面绘制 HTML 的部分比较好理解 。上面代码中还实现了一个输入框的功能,并为它添加了一个监听器 。每当输入框内容有修改,OnInputChange方法就会调用,g.Update()会使该组件重新渲染显示 。
最后将该组件挂载到路径/上 。
编写 WebAssembly 程序之后,需要使用交叉编译的方式将它编译为.wasm文件:
nbsp;GOARCH=wasm GOOS=js go build -o app.wasm
如果编译出现错误 , 使用go version命令检查 Go 是否是 1.14 或更新的版本 。
接下来,我们需要编写一个 Go Web 程序使用这个app.wasm:
package mainimport ("log""net/http""github.com/maxence-charriere/go-app/v6/pkg/app")func main() {h := &app.Handler{Title:"Go-App",Author: "dj",}if err := http.ListenAndServe(":8080", h); err != nil {log.Fatal(err)}}
go-app提供了一个app.Handler结构 , 它会自动查找同目录下的app.wasm(这也是为什么将目标文件设置为app.wasm的原因) 。然后我们将前面编译生成的app.wasm放到同一目录下,执行该程序:
nbsp;go run main.go
默认显示”Hello World”:
在输入框中输入内容之后,显示会随之变化:
可以看到,go-app为我们设置了一些基本的样式,网页图标等 。
简单原理
GitHub 上这张图很好地说明了 HTTP 请求的执行流程:
用户请求先到app.Handler层看优酷显示错误代码,它会去app.wasm中执行相关的路由逻辑、去磁盘上查找静态文件 。响应经由app.Handler中转返回给用户 。用户就看到了app.wasm渲染的页面 。实际上,在本文中我们只需要编写一个 Go Web 程序,每次编写新的 WebAssembly 之后,将新编译生成的 app.wasm 文件拷贝到 Go Web 目录下重新运行程序即可 。注意,如果页面未能及时刷新,可能是缓存导致的,可尝试清理浏览器缓存 。
组件
自定义一个组件很简单,只需要将app.Compo内嵌到结构中即可 。实现Render()方法可定义组件的外观,实际上app.Compo有一个默认的外观,我们可以这样来查看:
func main() {app.Route("/app", &app.Compo{})app.Run()}
编译生成app.wasm之后 , 一开始的 Go Web 程序不需要修改 , 直接运行 , 打开浏览器查看:
事件处理
在快速开始中,我们还介绍了如何使用事件 。使用声明式语法app.Input().OnChange(handler)即可监听内容变化 。事件处理函数必须为func (src app.Value, e app.Event)类型,app.Value是触发对象,app.Event是事件的内容 。通过app.Value我们可以得到输入框内容、选择框的选项等信息,通过app.Event可以得到事件的信息,是鼠标事件、键盘事件还是其它事件:
type ShowSelect struct {app.Compooption string}func (s *ShowSelect) Render() app.UI {return app.Div().Body(app.Main().Body(app.H1().Body(app.If(s.option == "",app.Text("Please select!"),).Else(app.Text("You've selected "+s.option),),),),app.Select().Body(app.Option().Body(app.Text("apple"),),app.Option().Body(app.Text("orange"),),app.Option().Body(app.Text("banana"),),).OnChange(s.OnSelectChange),)}func (s *ShowSelect) OnSelectChange(src app.Value, e app.Event) {s.option = src.Get("value").String()s.Update()}func main() {app.Route("/", &ShowSelect{})app.Run()}
上面代码显示一个选择框 , 当选项改变时上面显示的文字会做相应的改变 。初始时:
选择后:
嵌套组件
组件可以嵌套使用看优酷显示错误代码 , 即在一个组件中使用另一个组件 。渲染时将内部的组件表现为外部组件的一部分:
type Greeting struct {app.Compo}func (g *Greeting) Render() app.UI {return app.P().Body(app.Text("Hello, "),&Name{name: "dj"},)}type Name struct {app.Componame string}func (n *Name) Render() app.UI {return app.Text(n.name)}func main() {app.Route("/", &Greeting{})app.Run()}
上面代码在组件Greeting中内嵌了一个Name组件,运行显示:
生命周期
go-app提供了组件的 3 个生命周期的钩子函数:
例如:
type Foo struct {app.Compo}func (*Foo) Render() app.UI {return app.P().Body(app.Text("Hello World"),)}func (*Foo) OnMount() {fmt.Println("component mounted")}func (*Foo) OnNav(u *url.URL) {fmt.Println("component navigated:", u)}func (*Foo) OnDismount() {fmt.Println("component dismounted")}func main() {app.Route("/", &Foo{})app.Run()}
编译运行,在浏览器中打开页面,打开浏览器控制台观察输出:
component mountedcomponent navigated: http://localhost:8080/
编写 HTML
在前面的例子中我们已经看到了如何使用声明式语法编写 HTML 页面 。go-app为所有标准的 HTML 元素都提供了相关的类型 。创建这些对象的方法名也比较好记,就是元素名的首字母大写 。如app.Div()创建一个div元素,app.P()创建一个p元素,app.H1()创建一个h1元素等等 。在go-app中,这些结构都是暴露出对应的接口供开发者使用的,如div对应HTMLDiv接口:
type HTMLDiv interface {Body(nodes ...Node) HTMLDivClass(v string) HTMLDivID(v string) HTMLDivStyle(k, v string) HTMLDivOnClick(h EventHandler) HTMLDivOnKeyPress(h EventHandler) HTMLDivOnMouseOver(h EventHandler) HTMLDiv}
可以看到每个方法都返回该HTMLDiv自身,所以支持链式调用 。调用这些方法可以设置元素的各方面属性:
和设置事件监听:
例如下面代码:
app.Div().Body(app.H1().Body(app.Text("Title"),),app.P().ID("id").Class("content").Body(app.Text("something interesting"),),)
相当于 HTML 代码:
titlesomething interesting
原生元素
我们可以在app.Raw()中直接写 HTML 代码,app.Raw()会生成对应的app.UI返回:
svg := app.Raw(``)
但是这种写法是不安全的,因为没有检查 HTML 的结构 。
条件
我们在最开始的例子中就已经用到了条件语句,条件语句对应 3 个方法:If()/ElseIf()/Else() 。
If和ElseIf接收两个参数,第一个参数为bool值 。如果为true,则显示第二个参数(类型为app.UI) , 否则不显示 。
Else必须在If或ElseIf后使用,如果前面的条件都不满足,则显示传入Else方法的app.UI:
type ScoreUI struct {app.Composcore int}func (c *ScoreUI) Render() app.UI {return app.Div().Body(app.If(c.score >= 90,app.H1().Style("color", "green").Body(app.Text("Good!"),),).ElseIf(c.score >= 60,app.H1().Style("color", "orange").Body(app.Text("Pass!"),),).Else(app.H1().Style("color", "red").Body(app.Text("fail!"),),),app.Input().Value(c.score).Placeholder("Input your score?").AutoFocus(true).OnChange(c.OnInputChange),)}func (c *ScoreUI) OnInputChange(src app.Value, e app.Event) {score, _ := strconv.ParseUint(src.Get("value").String(), 10, 32)c.score = int(score)c.Update()}func main() {app.Route("/", &ScoreUI{})app.Run()}
上面我们根据输入的分数显示对应的文字,90及以上显示绿色的Good!,60-90之间显示橙色的Pass! , 小于60显示红色的Fail! 。下面是运行结果:
Range
假设我们要编写一个 HTML 列表 , 当前有一个字符串的切片 。如果一个个写就太繁琐了,而且不够灵活,且容易出错 。这时就可以使用Range()方法了:
type RangeUI struct {app.Componame string}func (*RangeUI) Render() app.UI {langs := []string{"Go", "JavaScript", "Python", "C"}return app.Ul().Body(app.Range(langs).Slice(func(i int) app.UI {return app.Li().Body(app.Text(langs[i]),)}),)}func main() {app.Route("/", &RangeUI{})app.Run()}
Range()可以对切片或map中每一项生成一个app.UI,然后平铺在某个元素的Body()方法中 。
运行结果:
上下文菜单
在go-app中 , 我们可以很方便的自定义右键弹出的菜单,并且为菜单项编写响应:
type ContextMenuUI struct {app.Componame string}func (c *ContextMenuUI) Render() app.UI {return app.Div().Body(app.Text("Hello, World"),).OnContextMenu(c.OnContextMenu)}func (*ContextMenuUI) OnContextMenu(src app.Value, event app.Event) {event.PreventDefault()app.NewContextMenu(app.MenuItem().Label("item 1").OnClick(func(src app.Value, e app.Event) {fmt.Println("item 1 clicked")}),app.MenuItem().Separator(),app.MenuItem().Label("item 2").OnClick(func(src app.Value, e app.Event) {fmt.Println("item 2 clicked")}),)}func main() {app.Route("/", &ContextMenuUI{})app.Run()}
我们在OnContextMenu中调用了event.PreventDefault()阻止默认菜单的弹出 。看运行结果:
点击菜单项,观察控制台输出~
app.Handler
上面我们都是使用go-app内置的app.Handler处理客户端的请求 。我们只设置了简单的两个属性Author和Title 。app.Handler还有其它很多字段可以定制:
type Handler struct {Author stringBackgroundColor stringCacheableResources []stringDescription stringEnv EnvironmentIcon IconKeywords []stringLoadingLabel stringName stringRawHeaders []stringRootDir stringScripts []stringShortName stringStyles []stringThemeColor stringTitle stringUseMinimalDefaultStyles boolVersion string}
CSS 和 JS 文件必须在app.Handler中声明 。下面是一个示例app.Handler:
h := &app.Handler{Name:"Luck",Author:"Maxence Charriere",Description: "Lottery numbers generator.",Icon: app.Icon{Default: "/web/icon.png",},Keywords: []string{"EuroMillions","MEGA Millions","Powerball",},ThemeColor:"#000000",BackgroundColor: "#000000",Styles: []string{"/web/luck.css",},Version: "wIKiverSiON",}
本文代码
本文中 WebAssembly 代码都在各自的目录中 。Go Web 演示代码在 web 目录中 。先进入某个目录 , 使用下面的命令编译:
nbsp;GOARCH=wasm GOOS=js go build -o app.wasm
然后将生成的app.wasm拷贝到web目录:
nbsp;cp app.wasm ../web/
切换到 web 目录,启动服务器:
nbsp;cd ../web/nbsp;go run main.go
总结
本文介绍如何使用go-app编写基于 WebAssembly 的 Web 应用程序 。可能有人会觉得,go-app编写 HTML 的方式有点繁琐 。但是我们可以写一个转换程序将普通的 HTML 代码转为go-app代码,感兴趣可以自己实现一下 。WebAssembly 技术非常值得关注一波~
【Go 每日一库之 go-app:WebAssembly 技术使用】本文到此结束,希望对大家有所帮助!
猜你喜欢
- 玩卡大神们遵循的信用卡潜规则是什么呢?
- 有哪些使用信用卡的安全常识?
- ?内蒙古鄂尔多斯去年人均GDP突破25万元 内蒙古很发达吗?
- 我亲身经历的骗局系列之十一
- 试管婴儿胚胎移植术后,需要“躺平”吗?
- 「手机技巧」安卓和苹果通吃,简单几步教你安装强大的油猴脚本
- 女人分娩,宫口宫缩后,痛到不想生,一张图带你看宫缩十指有多大
- ?夏季2008年奥运会在北京举办 是第几届
- 九鹏溪
