本文只介绍template的语法和用法,关于template包的函数、方法、template的结构和原理,见:深入剖析Go template。
入门示例
以下为test.html文件的内容,里面使用了一个template语法{{.}}
。
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Go Web</title>
</head>
<body>
{{ . }}
</body>
</html>
以下是test.html同目录下的一个go web程序:
package main
import (
"html/template"
"net/http"
)
func tmpl(w http.ResponseWriter, r *http.Request) {
t1, err := template.ParseFiles("test.html")
if err != nil {
panic(err)
}
t1.Execute(w, "hello world")
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/tmpl", tmpl)
server.ListenAndServe()
}
前面的html文件中使用了一个template的语法{{.}}
,这部分是需要通过go的template引擎进行解析,然后替换成对应的内容。
在go程序中,handler函数中使用template.ParseFiles("test.html")
,它会自动创建一个模板(关联到变量t1上),并解析一个或多个文本文件(不仅仅是html文件),解析之后就可以使用Execute(w,"hello world")
去执行解析后的模板对象,执行过程是合并、替换的过程。例如上面的{{.}}
中的.
会替换成当前对象"hello world",并和其它纯字符串内容进行合并,最后写入w中,也就是发送到浏览器"hello world"。
本文不解释这些template包的函数、方法以及更底层的理论知识,本文只解释template的语法,如果觉得这些无法理解,或者看不懂官方手册,请看深入剖析Go template。
关于点"."和作用域
在写template的时候,会经常用到"."。比如{{.}}
、{{len .}}
、{{.Name}}
、{{$x.Name}}
等等。
在template中,点"."代表当前作用域的当前对象。它类似于java/c++的this关键字,类似于perl/python的self。如果了解perl,它更可以简单地理解为默认变量$_
。
例如,前面示例test.html中{{.}}
,这个点是顶级作用域范围内的,它代表Execute(w,"hello worold")
的第二个参数"hello world"。也就是说它代表这个字符串对象。
再例如,有一个Person struct。
type Person struct {
Name string
Age int
}
func main(){
p := Person{"longshuai",23}
tmpl, _ := template.New("test").Parse("Name: {{.Name}}, Age: {{.Age}}")
_ = tmpl.Execute(os.Stdout, p)
}
这里{{.Name}}
和{{.Age}}
中的点"."代表的是顶级作用域的对象p,所以Execute()方法执行的时候,会将{{.Name}}
替换成p.Name
,同理{{.Age}}
替换成{{p.Age}}
。
但是并非只有一个顶级作用域,range、with、if等内置action都有自己的本地作用域。它们的用法后文解释,这里仅引入它们的作用域来解释"."。
例如下面的例子,如果看不懂也没关系,只要从中理解"."即可。
package main
import (
"os"
"text/template"
)
type Friend struct {
Fname string
}
type Person struct {
UserName string
Emails []string
Friends []*Friend
}
func main() {
f1 := Friend{Fname: "xiaofang"}
f2 := Friend{Fname: "wugui"}
t := template.New("test")
t = template.Must(t.Parse(
`hello {{.UserName}}!
{{ range .Emails }}
an email {{ . }}
{{- end }}
{{ with .Friends }}
{{- range . }}
my friend name is {{.Fname}}
{{- end }}
{{ end }}`))
p := Person{UserName: "longshuai",
Emails: []string{"a1@qq.com", "a2@gmail.com"},
Friends: []*Friend{&f1, &f2}}
t.Execute(os.Stdout, p)
}
输出结果:
hello longshuai!
an email a1@qq.com
an email a2@gmail.com
my friend name is xiaofang
my friend name is wugui
这里定义了一个Person结构,它有两个slice结构的字段。在Parse()方法中:
- 顶级作用域的
{{.UserName}}
、{{.Emails}}
、{{.Friends}}
中的点都代表Execute()的第二个参数,也就是Person对象p,它们在执行的时候会分别被替换成p.UserName、p.Emails、p.Friends。
- 因为Emails和Friend字段都是可迭代的,在
{{range .Emails}}...{{end}}
这一段结构内部an email {{.}}
,这个"."代表的是range迭代时的每个元素对象,也就是p.Emails这个slice中的每个元素。
- 同理,with结构内部
{{r