Go 语言应用之 template

本文介绍了 Go 语言中的 `text/template` 和 `html/template` 包,用于数据驱动生成模板化的文本。通过示例展示了如何使用这两个包来处理模板文件,包括遍历数据结构并生成输出。文章还提及了模板在 HTML 渲染中的应用,并提供了一个简单的 HTTP 服务器来展示效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在看《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 结构体,它包含两个字段 NumberSquare,这正好对应模板文件中的要输出的两个字段;接着是 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 端口:

html template

是不是非常酷炫!

整体来看,两个 template 使用起来是非常简单的,当然,这是因为它们背后干了很多脏活累活。如果想要深入研究,可以先去看包的说明,再去研究源码。

看完本文,再回头看看上次写的关于 redir 文章里的 template 文件,还是挺有意思的。不过,由于欧神加了些 CSS,所以最终呈现出来的表格样式更好看:

golang.design/s/

这就是今天的全部内容,我们下期再见!大家记得留言让欧神发视频~

参考资料

[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/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值