1、 模板原理
1.1 模板和模板引擎
在基于MVC模型的Wb架构中,我们常将不变的部分提出成为模板,而可变部分由后端程序提供数据,借助模板引擎渲染来生成动态网页。
模板可以被理解为事先定义好的TML文档。模板渲染可以被简单理解为文本替换操作一使用相应的数据去替换HTML文档中事先准备好的标记。
模板的诞生是为了将显示与数据分离(即前后端分离)。模板技术多种多样,但其本质是将模板文件和数据通过模板引擎生成最终的HTML文档。模板引擎很多,PHP的Smarty、Node.js的jade等都很好使用。
1.2 Go语言模板引擎
Go语言内置了文本模板引擎text/template包,以及用于生成HTML文档的html/template包。它们的使用方法类似,可以简单归纳如下:
- 模板文件的后缀名通常是.tmpl和.tpl(也可以使用其他的后缀),必须使用UTF-8编码。
- 模板文件中使用
{{
和}}
来包裹和标识需要传入的数据。 - 传给模板的数据可以通过点号
.
来访问。如果是复合类型的数据,则可以通过{{.FieldName}}
来访问它的字段。 - 除
{{
和}}
包裹的内容外,其他内容均不做修改原样输出。
Go语言模板引擎的使用分为:定义模板文件、解析模板文件和渲染模板文件。
1. 定义模板文件。
定义模板文件是指,按照相应的语法规则去定义模板文件。
2. 解析模板文件。
html/template包提供了以下方法来解析模板文件,获得模板对象。可以通过New()函数创建模板对象,并为其添加一个模板名称。New()函数的定义如下:
func New(name string) *Template
可以使用Parse()方法来创建模板对象,并完成解析模板内容。Parse()方法的定义如下:
func (t *Template)Parse(src string)(*Template,error)
如果要解析模板文件,则使用ParseFiles()函数,该函数会返回模板对象。该函数的定义如下:
func ParseFiles(filenames ...string)(*Template,error)
如果要批量解析文件,则使用ParseGlob()函数。该函数的定义如下:
func ParseGlob(pattern string)(*Template,error)
可以使用ParseGlob()函数来进行正则匹配,比如在当前解析目录下有以a开头的模板文件,则使用template.ParseGlob(“a*”)即可。
3. 渲染模板文件。
html/template包提供了Execute(()和ExecuteTemplate)方法来渲染模板。这两个方法的定义如下:
func (t *Template)Execute(wr io.Writer, data interface(})error {}
func (t *Template)ExecuteTemplate(wr io.Writer,name string, data interface(})error {}
在创建New()函数时就为模板对象添加了一个模板名称,执行Execute()方法后会默认去寻找该名称进行数据融合。
使用ParseFiles()函数可以一次加载多个模板,此时不可以使用Execute()来执行数据融合,可以通过ExecuteTemplate()方法指定模板名称来执行数据融合。
2、使用html/template包
2.1、第一个模板
在Go语言中,可以通过将模板应用于一个数据结构(即把该数据结构作为模板的参数)来执行并输出HTML文档。
模板在执行时会遍历数据结构,并将指针指向运行中的数据结构中的“.”的当前位置。
用作模板的输入文本必须是UTF-8编码的文本。“Action”是数据运算和控制单位,“Action"由“{{”和“}}”界定;在Action之外的所有文本都会不做修改地复制到输出中。Action内部不能有换行,但注释可以有换行。
template_example.tmpl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>模板使用示例</title>
</head>
<body>
<p>示例:{{.}}</p>
</body>
</html>
/*
解析和渲染模板
*/
func main() {
http.HandleFunc("/", helloHandleFunc)
http.ListenAndServe(":8080", nil)
}
func helloHandleFunc(w http.ResponseWriter, r *http.Request) {
//解析模板
t, err := template.ParseFiles("./src/bookWebPro/chapter2/template_example.tmpl")
if err != nil {
fmt.Println("template parsefile failed, err:", err)
return
}
//渲染模板
name := "hello world"
t.Execute(w, name)
}
浏览器访问:127.0.0.1:8080:
2.2 模板语法
模板语法都包含在“{{”和“}}”中间,其中“{{}}”中的点表示当前对象。在传入一个结构体对象时,可以根据“.”来访问结构体的对应字段。例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hello</title>
</head>
<body>
<p>Hello {{.Name}}</p>
<p>性别: {{.Gender}}</p>
<p>年龄: {{.Age}}</p>
</body>
</html>
func main() {
http.HandleFunc("/", sayHello)
http.ListenAndServe(":8080", nil)
}
type UserInfo struct {
Name string
Gender string
Age int
}
func sayHello(w http.ResponseWriter, r *http.Request) {
//解析模板
t, err := template.ParseFiles("./src/bookWebPro/chapter2/template_example2.tmpl")
if err != nil {
fmt.Println("template parsefile failed, err:", err)
return
}
//渲染模板
user := UserInfo{
Name: "李四",
Gender: "男",
Age: 28,
}
t.Execute(w, user)
}
同理,传入的变量是map时,也可以在模板文件中通过"{{.}}"的键值来取值。
1. 注释:
在Go语言中,HTML模板的注释结构如下:
({/*这是一个注释,不会解析*/}}
注释在执行时会被忽略。可以有多行注释。注释不能嵌套,并且必须紧贴分界符始止。
2. 管道(pipeline):
管道是指产生数据的操作。比如“{{.}}”“{{.Name}}”等。Go的模板语法中支持使用管道符号“|”链接多个命令,用法和UNX下的管道类似:“|”前面的命令会将运算结果(或返回值)传递给后一个命令的最后一个位置。
3.变量:
在Action里可以初始化一个变量来捕获管道的执行结果。初始化语法如下:
$variable := pipeline
其中$variable是变量的名字。声明变量的Action不会产生任何输出。
4.条件判断:
Go模板语法中的条件判断有以下几种:
{{if pipeline}} T1 {{end}}
{{if pipeline}} T1 {{else}} T0 {{end}}
{{if pipeline}} T1 ({else if pipeline}} T0 {{end}}
5. range关键字:
在Go的模板语法中,使用range关键字进行遍历,其中pipeline的值必须是数组、切片、字典或者通道。其语法以“{{range pipeline}}”开头,以“{{end}}”结尾,形式如下:
{{range pipeline}} T1 {{end}}
如果pipeline的值其长度为0,则不会有任何输出。中间也可以有“{{else}}”,形如:
{{range pipeline}} T1 {{else)} T0 ((end}}
如果pipeline的值其长度为0,则会执行T0。
range关键字的使用示例如下。
//range使用示例
func main() {
//创建一个模板
rangeTemplate := `
{{if .Kind}}
{{range $i, $v := .MapContent}}
{{$i}} => {{$v}}, {{$.OutsideContent}}
{{end}}
{{else}}
{{range .MapContent}}
{{.}}, {{$.OutsideContent}}
{{end}}
{{end}}`
str1 := []string{"第一次range", "用index和value"}
str2 := []string{"第二次range", "没有用index和value"}
type Content struct {
MapContent []string
OutsideContent string
Kind bool
}
var contens = []Content{
{str1, "第一次外面的内容", true},
{str2, "第二次外面的内容", false},
}
//创建模板并将字符解析进去
t := template.Must(template.New("range").Parse(rangeTemplate))
//接收并执行
for _, c := range contens {
err := t.Execute(os.Stdout, c)
if err != nil {
log.Panicln("executing template:", err)
}
}
}
0 => 第一次range, 第一次外面的内容
1 => 用index和value, 第一次外面的内容
第二次range, 第二次外面的内容
没有用index和value, 第二次外面的内容
6. with关键字:
在Go的模板语法中,with关键字和if关键字有点类似,“{{with}}”操作仅在传递的管道不为空时有条件地执行其主体。形式如下:
{{with pipeline}} T1 {{end}}
如果pipeline为空,则不产生输出。中间也可以加入“{{else}}”,形如:
{{with pipeline}} T1 {{else}}T0 ({end}}
如果pipeline为空,则不改变“.”并执行T0,否则将“.”设为pipeline的值并执行T1。
7. 比较函数:
布尔函数会将任何类型的零值视为假,将其余视为真。下面是常用的二元比较运算符:
eq //如果argl=arg2,则返回真
ne //如果arg1!=arg2,则返回真
lt //如果arg1<arg2,则返回真
le //如果arg1<=arg2,则返回真
gt //如果arg1>arg2,则返回真
ge //如果arg1>=arg2,.则返回真
为了简化多参数相等检测,eq(只有eq)可以接受2个或更多个参数,它会将第1个参数和其余参数依次比较,形式如下:
((eq arg1 arg2 arg3}}
即只能做如下比较:
arg1==arg2 I|argl==arg3
比较函数只适用于基本类型(或重定义的基本类型,如“type Balance float32”)。但整数和浮点数不能互相比较。
8. 预定义函数:
函数名 | 功能 |
---|---|
and | 函数返回其第1个空参数或者最后一个参数,即“and x y”等价于“if x then y else x”。所有参数都会执行 |
or | 返回第一个非空参数或者最后一个参数,即“or x y”等价于“if x then × else y”。所有参数都会执行 |
not | 返回其单个参数的布尔值“不是” |
len | 返回其参数的整数类型长度 |
index | 执行结果为index()函数后第1个参数以第1个参数后面剩下的参数为索引指向的值,例如“index y 1 2 3”返回y[1][2][3]的值。每个被索引的主体必须是数组、切片或者字典 |
即fmt.Sprint | |
printf | 即fmt.Sprintf |
printin | 即fmt.Sprintln |
html | 返回其参数文本表示的HTML逸码等价表示 |
urlquery | 返回其参数文本表示的可嵌入URL查询的逸码等价表示 |
js | 返回其参数文本表示的JavaScript逸码等价表示 |
call | 执行结果是调用第1个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数;如“cal .X .Y 1 2”等价于Go语言里的dot.X.Y(1,2);其中丫是函数类型的字段或者字典的值,或者其他类似情况; call的第1个参数的执行结果必须是函数类型的值,和预定义函数(如prit0)明显不同;该函数类型值必须有1个或2个返回值。如果有2个返回值,则后一个必须是error接口类型;如果有2个返回值的方法返回的error非nil,则模板执行会中断并返回给调用模板执行者该错误 |
8.自定义函数:
Go语言的模板支持自定义函数。自定义函数通过调用Fucs()方法实现,其定义如下:
func (t *Template)Funcs(funcMap FuncMap)*Template
Funcs()方法向模板对象的函数字典里加入参数funcMap内的键值对。如果funcMap的某个键值对的值不是函数类型,或者返回值不符合要求,则会报panic错误,但可以对模板对象的函数列表的成员进行重写。方法返回模板对象以便进行链式调用。FuncMap类型的定义如下:
type FuncMap map[string]interface(}
FuncMap类型定义了函数名字符串到函数的映射,每个函数都必须有1个或2个返回值。如果有2个返回值,则后一个必须是error接口类型;如果有2个返回值的方法返回error非nil,则模板执行会中断并返回该错误给调用者。
在执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用Fucs()方法添加函数到模板里。其使用示例如下。
/*
自定义函数
*/
func main() {
http.HandleFunc("/", sayHello2)
http.ListenAndServe(":8080", nil)
}
func Welcome() string {
return "Welcome"
}
func Doing(name string) string {
return name + ", Learning Go Web template "
}
func sayHello2(w http.ResponseWriter, r *http.Request) {
htmlByte, err := ioutil.ReadFile("./src/bookWebPro/chapter2/funcs.html")
if err != nil {
fmt.Println("read html failed, err:", err)
return
}
//自定义一个匿名模板函数
loveGo := func() string {
return "欢迎学习Go语言"
}
//链式操作在Parse()方法之前调用Funcs()函数,用来添加自定义的loveGo函数
tmpl1, err := template.New("funcs").Funcs(template.FuncMap{"loveGo": loveGo}).Parse(string(htmlByte))
if err != nil {
fmt.Println("create template failed, err:", err)
return
}
funcMap := template.FuncMap{
//声明要使用的函数
"Welcome": Welcome,
"Doing": Doing,
}
name := "Shirdon"
tmpl2, err2 := template.New("test").Funcs(funcMap).Parse("{{Welcome}}\n{{Doing .}}\n")
if err2 != nil {
panic(err2)
}
//使用user渲染模板,并将结果写入w
tmpl1.Execute(w, name)
tmpl2.Execute(w, name)
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>tmpl test</title>
</head>
<body>
<h1>{{loveGo}}</h1>
</body>
</html>
9. 使用嵌套模板:
html/template包支持在一个模板中嵌套其他模板。被嵌套的模板可以是单独的文件,也可以是通过“define”关键字定义的模板。通过“define”关键字可以直接在待解析内容中定义一个模板。例如定义一个名称为name的模板的形式如下:
{{define "name"}} T {{end}}
通过“template”关键字来执行模板。例如,执行名为“name”的模板的形式如下:
{{template "name"}}
{{template "name" pipeline}}
“block”关键字等价于“define”关键字。“block”关键字用于定义一个模板,并在有需要的地方执行这个模板。其形式如下:
{{block "name" pipeline }} T {{end}}
等价于:先执行{{define “name”}} T {{end}},再执行{{template “name” pipeline}}。
创建用于模板嵌套的代码:
t.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>tmpl test</title>
</head>
<body>
<h1>测试嵌套template语法</h1>
<hr>
{{template "ul.html"}}
<hr>
{{template "ol.html"}}
</body>
</html>
{{define "ol.html"}}
<h1>这是ol.html</h1>
<ol>
<li>I love Go</li>
<li>I love java</li>
<li>I love c</li>
</ol>
{{end}}
ul.html
<ul>
<li>注释</li>
<li>日志</li>
<li>测试</li>
</ul>
//定义一个UserInfo
type UserInfoA struct {
Name string
Gender string
Age int
}
func tmplSample(w http.ResponseWriter, r *http.Request) {
tmpl, err := template.ParseFiles("./src/bookWebPro/chapter2/t.html", "./src/bookWebPro/chapter2/ul.html")
if err != nil {
fmt.Println("create template failed, err:", err)
return
}
user := UserInfoA{
Name: "张三",
Gender: "男",
Age: 28,
}
tmpl.Execute(w, user)
}
func main() {
http.HandleFunc("/", tmplSample)
http.ListenAndServe(":8080", nil)
}