最近在看《Mastering Go》英文版,这本书整体读起来比较顺畅,没有特别难的地方。如果你完整看完了深度解密系列文章或者其他源码相关文章的话,它甚至是比较简单的。但这本书的优点是它包含的内容非常全,很适合复习。比如我们今天讲的 template 相关的例子就来自于本书。
在正式内容开始之前,来说点近期发生的事。首先是上周发了两篇极客时间的推文,在推文前知会了一下 Go 语言中文网的站长 polarisxu 同学,使得他在感恩节那天发了一篇很肉麻的夸我的文章,并且在文章末尾放了码农桃花源公众号的二维码,一下子,很快啊,100 多读者就过来了,我没有防备。
那作为年轻人,我比较讲武德。礼尚往来,我也重点推荐下站长,他是北大毕业的高材生,创业公司 CTO,执掌 Go 语言中文网,同时主理个人原创公众号 polarisxu,产量很高,相比码农桃花源的周更,它基本是日更。二维码放在这里,大家可以关注一下~
说到推文,第 1 篇还好,阅读量基本达到了预期;第 2 篇就扑街了,虽然甲方爸爸没说啥,但作为一个有武德的年轻人还是再次推荐一下。推文开头由我本人“捉刀”:
4 年前我就知道华仔了,那时候我会用不熟练的 C++ 刷题,又要参与实验室的 Java 项目,面向对象也不太懂,两门语言有很多相似又有不同的地方,有很多不解……直到我在京东上搜到了《面向对象葵花宝典》这本书,看书名很可能不屑一顾,“什么玩意?”而错过一本好书。

我没有!我在通读了一遍之后,脑中很多问题都得到了解决,感觉非常爽!就像是某天突然得到了一本传世秘籍。这本书现在已经绝版,华仔修订了部分内容之后,改名《编程的逻辑》重新出版了,现在已经可以买了。
一年前找工作的时候,我又看了《从零开始学架构》一书(极客时间也有同名专栏),像是遇到了另一本秘籍。书里对高性能、高可用等的总结非常好,对 CAP 理论的各种“拨乱反正”也让人“大呼过瘾”。
由此感叹,华仔的水平不一般,总结能力更是超强啊。
最近,华仔又开了一门讲职场晋升的课,我是也第一时间买了专栏。在前天的直播中,华仔说自己在阿里是从 P5 一直晋升到 P9,并且每次都是一遍过。在晋升完全靠跳槽的今天显得多么不可思议。

极客时间上面有很多好课,我之前完整学过的觉得非常好的有:《Linux 性能优化》、《网络编程实战》、《从零开始学架构》等等,后面有机会再推荐……
再说个事,上周我无意中看到剪映(视频剪辑软件)出了个 mac 专业版,试用了一下,各种素材特效,还挺有意思的。第二天,突发奇想,可以让欧神剪一些关于他在德国生活的 vlog。但和他一聊,原来他刚去德国那阵确实拍过很多素材,但后来没搞起来……
什么?你不知道欧神是谁。去看看《Go 语言原本》[1]。
如果大家对欧神感兴趣,想看他拍的 vlog,请在文章末尾留言,如果留言的人数很多,我就可以“挟留言以令欧神”^_^。大家给力点,可以先去留个言再看后面的内容。
在讲技术内容之前呢,再说一下加我好友的方式,很简单,后台回复:加好友。
Go 语言内置了两个 template 相关的包:text/template[2]、html/template[3]。两者都是用数据驱动(data-driven)生成模板化的文本,不同的是后者用于生成 html 文本,它可以借助浏览器呈现更好的视觉效果。
Go 语言应用的系列文章,我将不会像深度解密系列那样深入到源码,用就完了!毕竟我们没那么多精力去研究所有源码,有了阅读源码的能力,碰到关键的地方再去仔细研究。
text template
我们先看模板文件(文件名是 text.gotext):
Calculating the squares of some integers
{{ range . }} The square of {{ printf "%d" .Number}} is {{ printf "%d" .Square}}
{{ end }}
应用模板后,第 1 行,会按原样输出;第 3 行我们看到有一些被 {{ }}
包裹的元素,range 表示遍历,既然是遍历,那么被遍历的对象就应该是:array, slice, map, or channel。dot
符号,即 .
表示的是当前元素;The square of
原样输出;printf
会将 Number 字段和 Square 字段打印出来;最后,{{ end }}
结束循环。
我们再来看对应的使用这个模板文件来产生一些输出的应用代码:
package main
import (
"fmt"
"os"
"text/template"
)
type Entry struct {
Number int
Square int
}
func main() {
if len(os.Args) != 2 {
fmt.Println("Need the template file!")
return
}
tFile := os.Args[1]
data := [][]int{{-1, 1}, {-2, 4}, {-3, 9}, {-4, 16}}
var Entries []Entry
for _, i := range data {
temp := Entry{Number: i[0], Square: i[1]}
Entries = append(Entries, temp)
}
t := template.Must(template.ParseGlob(tFile))
t.Execute(os.Stdout, Entries)
}
上面的程序非常简短:首先引入了 text/template
包;接着定义了 Entry
结构体,它包含两个字段 Number
和 Square
,这正好对应模板文件中的要输出的两个字段;接着是 main 函数,将 os.Args 的第 2 个元素赋值给了 tFile
,这意味着我们在命令运行以上代码的时候,需要将模板文件名带上;然后,构造 Entries 切片;最后两行代码是最核心的,它将 Entries 切片应用到模板文件。
函数 ParseGlob 会将 pattern 指示的文件解析成 Template:
func ParseGlob(pattern string) (*Template, error)
而 Must 函数则是一个工具函数,它将 ParseGlob 函数的 2 个返回值包装成 1 个返回值(这从它的函数声明可以看出来),以此来支持链式调用:
func Must(t *Template, err error) *Template
最后执行 Execute 函数将输出写到 os.Stdout,也就是终端。
我们运行以上的程序,注意加上文件名:
go run textT.go text.gotext
输出:
Calculating the squares of some integers
The square of -1 is 1
The square of -2 is 4
The square of -3 is 9
The square of -4 is 16
html template
同样的套路,我们来看 html template。模板文件(文件名 html.gohtml):
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Doing Maths in Go!</title>
<style>
html {
font-size: 14px;
}
table, th, td {
border: 2px solid blue;
}
</style>
</head>
<body>
<table>
<thead>
<tr>
<th>Number</th>
<th>Square</th>
</tr>
</thead>
<tbody>
{{ range . }}
<tr>
<td> {{ .Number }} </td>
<td> {{ .Square }} </td>
</tr>
{{ end }}
</tbody>
</table>
</body>
</html>
虽然文件很长,但其实 html 是比较好懂的,这里不多解释。核心仍然是由 range 包裹,来遍历结构体切片。
为了便于展示,我们启动一个 http server:
package main
import (
"fmt"
"html/template"
"net/http"
"os"
)
type Entry struct {
Number int
Square int
}
var Entries []Entry
var tFile string
func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Printf("Host: %s Path: %s\n", r.Host, r.URL.Path)
myT := template.Must(template.ParseGlob(tFile))
myT.ExecuteTemplate(w, tFile, Entries)
}
func main() {
if len(os.Args) != 2 {
fmt.Println("need specify template file")
return
}
tFile = os.Args[1]
data := [][]int{{-1, 1}, {-2, 4}, {-3, 9}, {-4, 16}}
for _, i := range data {
temp := Entry{Number: i[0], Square: i[1]}
Entries = append(Entries, temp)
}
http.HandleFunc("/", myHandler)
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println(err)
return
}
}
核心代码的解释可以参考 text/template,大同小异。
运行程序:
go run htmlT.go html.gohtml
我们用浏览器访问 8080 端口:

是不是非常酷炫!
整体来看,两个 template 使用起来是非常简单的,当然,这是因为它们背后干了很多脏活累活。如果想要深入研究,可以先去看包的说明,再去研究源码。
看完本文,再回头看看上次写的关于 redir 文章里的 template 文件,还是挺有意思的。不过,由于欧神加了些 CSS,所以最终呈现出来的表格样式更好看:

这就是今天的全部内容,我们下期再见!大家记得留言让欧神发视频~
参考资料
[1]
《Go 语言原本》: https://golang.design/under-the-hood/
[2]text/template: https://golang.org/pkg/text/template/
[3]html/template: https://golang.org/pkg/html/template/