28

使用Go开发前端应用

 4 years ago
source link: https://studygolang.com/articles/28135
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

我们知道,在目前各种容器化盛行的时代,Go在开发容器化应用当中,成为大家首选的后端开发语言。目前,最流弊的容器化管理编排系统k8s,几乎每个大的云厂商都在使用。而k8s就是Google使用go语言开发出来的。而现在,go已经可以用来开发前端语言了,有种“一切可以用go语言实现的功能,最终都会用go语言实现”的感觉。这篇文章主要用来介绍,用go语言如何入门前端开发。

go开发环境安装

首先,你需要先下载安装一下go。下载地址: golang.org/ 安装其实很简单,这里就不说了,安装完成之后,控制台执行下如下命令,确认下go的安装是否成功。

go version
复制代码
qm6zAv2.png!web

如果能够正常输出,证明你的环境已经安装好了,是不是很简单?

go为什么可以用于前端开发

go在1.11版本中,加入了对 WebAssembly 的体验支持,目前go的版本已经到了1.14了,可以说对于 WebAssembly 已经支持的非常好了。关于Go语言中 WebAssembly 的更多信息,可以查看官方的wiki: github.com/golang/go/w…

正因为go编写的代码可以转化为WebAssembly,而WebAssembly又是可以在任何现代浏览器中运行的二进制格式的语言,所以,使用Go来开发前端应用,也就成为了可能。

一个简单的demo入门

直接看代码:

比如你的html页面的代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="test">test</button>
</body>
</html>
复制代码

页面当中,有一个button元素,button的id为“test”。

下面来看下在Go语言中怎么获取这个元素。

package main

import (
	"syscall/js"
)

js.Global().Get("document").Call("getElementById", "test")

复制代码

在上面的代码中,我们调用“syscall/js”包里面,提供的方法,来获取document对象,并且调用document的getElementById方法来获取我们html页面中的button元素。但是到这里,其实什么都看不出来,我们尝试获取完button元素之后,将button的按钮文字修改为“changed by go”。

btn := js.Global().Get("document").Call("getElementById", "test")
btn.Set("innerHTML", "changed by go")
复制代码

写完之后,代码大概是上面这个样子,其他部分就不贴了。到这里,一个基本的demo算写完了,那怎么来测试呢?

首先我们需要将go的代码,编译成 WebAssembly,然后我们还需要用到go给我们提供的一个js库,这个是用来在js中,调用go编译生成的WebAssembly,然后执行里面的代码逻辑用的。

首先我们复制下go提供的js库到目录中。

在项目根目录下运行下面的命令:

cp $(go env GOROOT)/misc/wasm/wasm_exec.js .
复制代码

运行完之后,大概是这个样子。

biI7zmA.png!web

然后我们需要编译go代码成wasm格式。

使用下面的命令,将go代码编译成wasm格式。

GOOS=js GOARCH=wasm go build -o main.wasm main.go
复制代码

这里需要说明一下,GOOS和GOARCH这两个环境变量的作用。 在go里面,可以将go代码编译成各个平台的目标结果。比如GOOS,可以指定为windows或者linux等。在这里,还可以指定为js。

GOARCH表示系统架构,比如可以指定为amd64或者386等。在这里,还可以指定为wasm。

执行上面的命令之后,我们可以看到目录下多了一个wasm的文件。

RVVRNny.png!web

到这里,准备工作差不多了。我们需要在html中引入go提供的js库,然后去使用刚刚我们编译生成的main.wasm了。

修改html,如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <button id="test"></button>
</body>
<script src="./wasm_exec.js"></script>
<script>
    const go = new Go()
    WebAssembly.instantiateStreaming(fetch('app.wasm'), go.importObject)
        .then(result => go.run(result.instance))
</script>
</html>
复制代码

上面的代码,WebAssembly.instantiateStreaming方法直接从流式底层源编译和实例化WebAssembly模块。这是加载wasm代码一种非常有效的优化方式。

fetch就不用说了。

go.importObject 是一个对象,这个对象会被导入到 wasm的模块中,这样在wasm的模块中就可以访问到js对象。

在这里,go.importObject大概长这样子:

UJR7bur.png!web

看go提供的js库中的源码,里面有注释。

JzUbymz.png!web

这里的importObject主要是用来在wasm文件里面调用js代码的(在wasm里面调用js提供的方法),在go里面,主要使用来处理SP(Stack Pointer)的变更。

上面的代码准备好之后,我们可以启动一个http的服务,推荐使用http-server来启动, github.com/http-party/…

启动完成之后,访问 http://127.0.0.1:8080/

EV3ABfu.png!web

可以看到,访问之后,正确还在了我们的wasm文件,并且执行了我们之前用go写的代码,将button的文字改成了“changed by go”。

给按钮添加点击事件处理

上面的代码,我们只是在访问的时候,修改了按钮的文字,并没有别的任何操作,下面来看下如果,给按钮添加一个点击事件。

首先我们需要声明一个函数,用来作为点击事件的回调函数。

func main() {
	btn := js.Global().Get("document").Call("getElementById", "test")

	callback := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		fmt.Println(this)
		fmt.Println(args)
		fmt.Println("button clicked")
		return nil
	})

	btn.Set("innerHTML", "changed by go")
	btn.Call("addEventListener", "click", callback)

}

复制代码

上面的代码中,首先,通过调用js包的FuncOf创建了一个用于在js里面调用的函数,在FuncOf的参数里面,我们可以看到定义的回调函数,这个函数有两个参数,第一个参数代表你js调用的时候的this对象,第二个参数表示调用时候的参数。

添加完上面的代码之后,我们重新生成下wasm文件,然后刷新页面,点击下按钮,看下是否会输出“button clicked”这个字符串。

ruayaqM.png!web

点击完成之后,发现报错了,提示go程序已经退出,这是为啥呢?

看上面的代码,我们发现在main函数里面,执行完所有的代码之后,go的主线程就直接退出了,而我们使用js.FuncOf创建的回调函数,其实是在单独的一个goroutine里面执行的,主线程都退出了,那goroutine自然无法执行了。

为了解决这个错误,我们需要保证主线程不退出。 修改代码如下:

func main() {
	btn := js.Global().Get("document").Call("getElementById", "test")

	signal := make(chan int)

	var callback js.Func
	callback = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
		fmt.Println(this)
		fmt.Println(args)
		fmt.Println("button clicked")
		return nil
	})

	btn.Set("innerHTML", "changed by go")
	btn.Call("addEventListener", "click", callback)

	<- signal
}
复制代码

这里加了一个channel类型的变量,关于channel的知识,可以查看官方的文档,或者看我之前写的go学习笔记( juejin.im/post/5a2b4e…

这里使用channel主要用来防止go的主线程退出,在最后一句,<- signal , 表示从这个signal的通道中获取数据,但是我们可以看到,并没有地方给这个通道塞入数据,所以,主线程会一直阻塞在这里,这样,我们的事件回调才会正常执行。

看下正常执行的结果:

J3QRBfY.png!web

可以发现,我们给button注册的点击事件,可以正常触发,并且回调函数也正常执行了。

如果仔细看上面的代码,发现使用Go来操作dom的话,还是比较麻烦的, 比如每次获取一个dom元素都需要:

js.Global().Get("document").Call("getElementById", "test")
复制代码

还有,我们只能这样调用dom的方法:

btn.Call("addEventListener", "click", callback)
复制代码

这里方法名称作为了参数,很容易失误写错。

所有,社区就有人将这些操作给封装了起来,比如: godoc.org/honnef.co/g…

这个库。

Jj6BjyF.png!web

查看文档,这个时候发现跟我们平时使用js操作dom的写法就比较一致了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK