转载自品略图书馆 http://www.pinlue.com/article/2020/04/2703/0610243954637.html

简介

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):

$ mkdir go-app && cd go-app

$ go mod init

然后下载安装go-app包:

$ go get -u -v github.com/maxence-charriere/go-app/v6

至于Go module的详细使用,去看煎鱼大佬的Go Modules 终极入门。

首先,我们要编写 WebAssembly 程序:

package main

import "github.com/maxence-charriere/go-app/v6/pkg/app"

type Greeting struct {

app.Compo

name 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文件:

$ GOARCH=wasm GOOS=js go build -o app.wasm

如果编译出现错误,使用go version命令检查 Go 是否是 1.14 或更新的版本。

接下来,我们需要编写一个 Go Web 程序使用这个app.wasm:

package main

import (

"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放到同一目录下,执行该程序:

$ 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.Compo

option 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.Compo

name string

}

func (n *Name) Render() app.UI {

return app.Text(n.name)

}

func main() {

app.Route("/", &Greeting{})

app.Run()

}

上面代码在组件Greeting中内嵌了一个Name组件,运行显示:

生命周期

go-app提供了组件的 3 个生命周期的钩子函数:

OnMount:当组件插入到 DOM 时调用;

OnNav:当一个组件所在页面被加载、刷新时调用;

OnDismount:当一个组件从页面中移除时调用。

例如:

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 mounted

component 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) HTMLDiv

Class(v string) HTMLDiv

ID(v string) HTMLDiv

Style(k, v string) HTMLDiv

OnClick(h EventHandler) HTMLDiv

OnKeyPress(h EventHandler) HTMLDiv

OnMouseOver(h EventHandler) HTMLDiv

}

可以看到每个方法都返回该HTMLDiv自身,所以支持链式调用。调用这些方法可以设置元素的各方面属性:

Class:添加 CSS Class;

ID:设置 ID 属性;

Style:设置内置样式;

Body:设置元素内容,可以随意嵌套。div中包含h1和p,p中包含img等;

和设置事件监听:

OnClick:点击事件;

OnKeyPress:按键事件;

OnMouseOver:鼠标移过事件。

例如下面代码:

app.Div().Body(

app.H1().Body(

app.Text("Title"),

),

app.P().ID("id").

Class("content").Body(

app.Text("something interesting"),

),

)

相当于 HTML 代码:

<div>

<h1>title</h1>

<p id="id" class="content">

something interesting

</p>

</div>

原生元素

我们可以在app.Raw()中直接写 HTML 代码,app.Raw()会生成对应的app.UI返回:

svg := app.Raw(`

<svg width="100" height="100">

<circle cx="50" cy="50" r="40" stroke="green" stroke-width="4" fill="yellow" />

</svg>

`)

但是这种写法是不安全的,因为没有检查 HTML 的结构。

条件

我们在最开始的例子中就已经用到了条件语句,条件语句对应 3 个方法:If()/ElseIf()/Else()。

If和ElseIf接收两个参数,第一个参数为bool值。如果为true,则显示第二个参数(类型为app.UI),否则不显示。

Else必须在If或ElseIf后使用,如果前面的条件都不满足,则显示传入Else方法的app.UI:

type ScoreUI struct {

app.Compo

score 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.Compo

name 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.Compo

name 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 string

BackgroundColor string

CacheableResources []string

Description string

Env Environment

Icon Icon

Keywords []string

LoadingLabel string

Name string

RawHeaders []string

RootDir string

Scripts []string

ShortName string

Styles []string

ThemeColor string

Title string

UseMinimalDefaultStyles bool

Version string

}

Icon:设置应用图标;

Styles:CSS 样式文件;

Scripts:JS 脚本文件。

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 目录中。先进入某个目录,使用下面的命令编译:

$ GOARCH=wasm GOOS=js go build -o app.wasm

然后将生成的app.wasm拷贝到web目录:

$ cp app.wasm ../web/

切换到 web 目录,启动服务器:

$ cd ../web/

$ go run main.go

总结

本文介绍如何使用go-app编写基于 WebAssembly 的 Web 应用程序。可能有人会觉得,go-app编写 HTML 的方式有点繁琐。但是我们可以写一个转换程序将普通的 HTML 代码转为go-app代码,感兴趣可以自己实现一下。

Go 语言之 go-app相关推荐

  1. 利用PHP语言开发手机app后台服务器的框架是什么?或者说开发流程是怎么样的?

    最近正在做一个手机APP的服务端API开发,虽然是基于Ruby on Rails的,做的也不太专业,不过大致相通,希望能够给你一些启发. 首先,如果是比较简单的手机APP,例如新闻客户端这样的 不会涉 ...

  2. c语言入门自学免费app,C语言入门学习最新版下载-C语言入门学习app手机版v1.0.2 安卓版-腾飞网...

    C语言入门学习app手机版是一款c语言编程自学软件,零基础也可以学习,里面有海量教学视频,针对c语言不同程度的讲解都囊括其中.随时随地学习编程都可以,不用担心自己没有基础.还支持在手机上敲代码编程哦. ...

  3. php开发的app商城,如何利用PHP语言开发手机APP

    如何利用PHP语言开发手机APP 一般的PHP框架都可以用来做app后台服务器.因为原理上客户端从你这边拿的都是字符串数据,所以就算你不用框架也没有问题,不过会引发后续的问题.PHP提供API给客户端 ...

  4. c语言学习宝典老版,C语言学习宝典APP最新版下载_C语言学习宝典APP官方版5.6.7下载_QQ下载站...

    C语言学习宝典APP简介 C语言学习宝典是一款网上学习APP,这儿包括了C语言学习培训各环节专业知识,给你轻轻松松该把握C语言的关键专业知识,提升自己的工作能力,出示来C语言基本知识,让学员从入门学习 ...

  5. c语言入门自学手机版,c语言入门自学app下载-C语言入门学习 安卓版v1.0.2-PC6安卓网...

    C语言入门学习app是一款C语言零基础自学软件.C语言入门自学app提供海量精品学习资源,从小白入门到基础进阶都有,帮你轻松学习编程. 软件介绍 C语言入门学习app是一款专业的编程入门学习App,致 ...

  6. c语言入门自学手机版,C语言入门学习app下载-C语言入门学习app最新版下载 V1.0.2-友情手机站...

    C语言入门学习app是一款0基础自学软件,这里有着丰富C语音相关课程学习,大家在这里是可以便捷搜索查找,随时都是可以找到适合感兴趣课程学习,都是一些优质课程知识提供大家,学员在这里是可以高效学习,海恩 ...

  7. c语言程序设计库搜索app,C语言编程宝典app

    C语言编程宝典app帮助学生能够更好的去进行编程知识的学习,拥有非常详细的c语言的学习知识,帮助用户去从c语言的入门学习到c语言的掌握,能够帮助用户去应对给类型的校内c语言考试,并且还能够为用户学习其 ...

  8. php语言能开发app吗_怎么利用PHP框架语言开发手机app?

    原标题:怎么利用PHP框架语言开发手机app? 一般的PHP框架都可以用来做app后台服务器.因为原理上客户端从你这边拿的都是字符串数据,所以就算你不用框架也没有问题,不过会引发后续的问题.PHP提供 ...

  9. C语言学习宝典下载,C语言学习宝典app下载-C语言学习宝典 v5.6.4 手机版 - 下载吧...

    C语言学习宝典app是一款可以使用手机就能编程的软件:这款软件包含了C语言课程的全部章节内容,含有C语言计算机二级考试的题库和考试的历年真题,并且具有C语言数据类型.控制语句.循环.数组等基础,以及C ...

  10. php开发安卓应用程序,如何利用PHP语言开发手机APP

    一般的PHP框架都可以用来做app后台服务器.因为原理上客户端从你这边拿的都是字符串数据,所以就算你不用框架也没有问题,不过会引发后续的问题.PHP提供API给客户端就好了,API描述方式有很多种:R ...

最新文章

  1. adb shell root
  2. 匠心功能农业-农业大健康·弘本农业:对话农民丰收节交易会
  3. 【机器学习课程笔记(吴恩达)】1.3 监督学习
  4. .NET Core 2.1路线图
  5. 栈的应用--括号匹配的检验
  6. STM32之独立看门狗原理
  7. 关于索爱MT15i连接win7——MTP USB驱动无法安装
  8. 3-单一职责原则+4-开放封闭原则+5依赖倒转原则
  9. mongodb 索引1
  10. PLC编程器的功能有什么功能?
  11. 酒店的月收入报表java_统计报表_宾馆明细收入报表
  12. Docker之Jitsi Meet视频会议服务
  13. 生成带二维码图片并通过微信分享
  14. 计算机科学终审多长时间,一些计算机科学核心期刊的投稿经验
  15. 【数据可视化应用】绘制峰峦图(附R语言代码)
  16. wav,flac,mp3,ogg等的区别
  17. 【x86架构】x86上的那些不明觉厉的功能
  18. Kafka(三):kafka消费者
  19. 深信服 2021 面试总结
  20. open FIFO for write returns “No such device or address”

热门文章

  1. Python一行代码实现1到100之和
  2. No installations recognized以及nvm use失败问题
  3. 国外无限php空间,关于无限空间和美国PHP主机不限制流量存储的认知
  4. APP开发者应办理许可或备案手续
  5. 计算机网络:学习笔记(持续更新)
  6. SRC挖掘信息收集之JS文件中的秘密
  7. vivo手机系统打印服务器,原来vivo手机自带扫描功能,文件瞬间电子化!几千的扫描仪省下了...
  8. python opencv 读取图片_Python opencv 读取图像
  9. 一篇文章,只用看三遍,终生不忘网络分层
  10. 如何重新设置苹果id密码_苹果手机ID密码忘了?别着急,这二种方法轻松帮你搞定!...