Golang模板引擎快速入门教程
Go语言内置了 text/template 和 html/template两个模板库,专门用于处理网页html模板。 html/template 是在 text/template 模板库的基础上增加了对html输出的安全处理,主要目的是为了防止被攻击。
下面通过一个例子介绍template的用法。
模版引擎使用流程:
-
编写模版代码
-
导入包
-
加载模版代码
-
根据模版参数渲染模版
1.快速入门例子
1.1.编写模版代码
将下面模版代码保存至views/demo.tpl文件中, 文件后缀名随意。
{{define "demo"}}
昵称: {{.Name}},
{{- if .IsWin}}
恭喜,大吉大利,今晚吃鸡!
{{- else}}
遗憾,鸡被吃光了!
{{- end}}
{{- end}}
define "模板名" 用于定义子模板,后面渲染模板会用到这个名字。
提示: 大家可以先跳过模版语法的细节,先了解大概怎么用,后面的章节会详细介绍模版语法。
1.2.导入包
import "text/template"
提示: 本文主要以text/template为例,如果要使用html/template直接替换包名就行,他们接口一样。
1.3.加载模版代码
// 加载模版代码,并且创建template对象t
// template.ParseGlob 函数加载views目录下的所有tpl为后缀的模版文件
// template.Must函数主要用于检测加载的模版有没有错误,有错误输出panic错误,并且结束程序。
t := template.Must(template.ParseGlob("./views/*.tpl"))
1.4.根据模版参数渲染模版
定义模版参数:
//这里定义一个struct类型的模版参数,实际上模版可以是任意类型数据
type GameStatus struct {
Name string
IsWin bool
}
提示: 因为只能传入一个模版参数,如果想传入多个模版参数,可以使用map或者struct类型。
初始化模板参数, 这里初始化一个参数数组,下面用于循环渲染模板。
var userStatus = []GameStatus{
{"大春", true},
{"NiuBee", false},
{"球球", true},
}
下面是根据userStatus 数组循环渲染模板
for _, u := range userStatus {
//根据参数u, 渲染命名为demo的模板,并且将渲染结果打印到标准输出
err := t.ExecuteTemplate(os.Stdout, "demo", u)
if err != nil {
log.Println("executing template:", err)
}
}
输出结果:
昵称: 大春,
恭喜,大吉大利,今晚吃鸡!
昵称: NiuBee,
遗憾,鸡被吃光了!
昵称: 球球,
恭喜,大吉大利,今晚吃鸡!
说明:根据不同的模版参数,渲染模版输出不同的页面内容,就是模版引擎的主要工作,目的是简化html模版输出工作。
golang template模版语法
模板表达式都包括在 {{ 和 }} 之间。
格式: {{ 模板表达式 }}
注释格式: {{/* 注释语法 */}}
1.删除空格处理
有时候我们输出表达式结果的时候,在表达式结果左右两边都会携带一些空格。
下面是删除表达式结果左右两边空格的语法:
-
在左边增加减号和空格,表示删除左边空格: {{- 模板表达式 }}
-
在右边增加空格和减号,表示删除右边空格: {{ 模板表达式 -}}
-
删除表达式左右两边空格的写法: {{- 模板表达式 -}}
例子:
"{{23 -}} < {{- 45}}"
输出:
"23<45"
23和45之间的空格都被删除了
2.模版参数
在模版中主要通过 点( . ) 引用模版参数。 因为在渲染模版的时候只能传入一个参数,所以点( . ) 正好可以代表模版参数的引用。
模版参数支持数字、布尔值、字符串、map、struct、数组类型。
下面分别介绍传入不同类型参数,模版代码如何引用参数。
2.1. int/bool/string类型参数
模版参数为int/bool/string类型,可以直接用点( . ) 引用即可。
{{.}}
例子:
//渲染demo模版,传入字符串参数 "欢迎访问tizi365.com"
t.ExecuteTemplate(os.Stdout, "demo", "欢迎访问tizi365.com")
{{.}} 模版代码,输出:
欢迎访问tizi365.com
2.2. map/struct类型参数
如果要传入多个模版参数,一般都使用map或者struct类型。 引用语法格式:
{{.字段名}}
嵌套的struct或者map类型引用语法格式:
{{.字段名1.字段名2}}
使用 点( . ) 连接多个字段名,就可以访问多层嵌套的struct/map类型数据.
例子:
//定义struct模版参数
type User struct {
Name string
}
type Order struct {
Id int
Title string
Customer User //嵌套字段
}
//初始化模版参数
food := Order{
Id: 10,
Title: "柠檬",
Customer: User{
Name: "李大春"
}
}
//渲染demo模版, 模版参数为food
t.ExecuteTemplate(os.Stdout, "demo", food)
模版代码:
商品名: {{.Title}}
用户名: {{.Customer.Name}}
输出结果:
商品名: 柠檬
用户名: 李大春
提示:如果模板参数传入数组,则需要循环语句处理输出,后面的章节会介绍range循环语句用法。
3.模版变量
在模版中也可以自定义变量, 类似golang使用:=符号定义变量,语法如下:
$变量名 := 数据
变量名需要以$美元符号开头。
例子:
定义变量
$title := "标题"
为变量赋值, 第二次为变量赋值,不需要冒号:
$title = "新标题"
引用变量
{{$title}}
Go 模板-流程控制语句
模版流程控制域名主要指if/range/with三种语句。
1. if语句
语法格式1:表达式为真,则执输出T1
{{if 表达式}} T1 {{end}}
语法格式2:表达式为真,则执输出T1, 否则输出T0
{{if 表达式}} T1 {{else}} T0 {{end}}
语法格式3:表达式1为真,则执输出T1, 否则如果表达式2为真,则输出T0
{{if 表达式1}} T1 {{else if 表达式2}} T0 {{end}}
2. range循环语句
语法格式1:
{{range 数组引用}}
{{.}} - 在range循环中,(点 .) 引用的是数组元素,而不是模版参数。
{{end}}
例子:
假设传入的模版参数如下, 这里我们直接传入一个数组:
titles := []string{"标题1", "标题2", "标题3"}
模版代码:
{{range .}}
{{.}}
{{end}}
输出:
标题1
标题2
标题3
提示:在range循环中,改变了 (点 . ) 的作用,(点 . )引用的是当前的数组元素。
语法格式2:
类似go语法循环语句中的range, 这里定义了两个局部变量$index和$element, 第一个变量代表索引(对于map类型数据就是代表map的key), 第二个变量代表元素值(对于map类型数据,代表key对应的值)
{{range $index, $element := 数组或者map的引用}}
索引: {{$index}}
元素值: {{$element}}
{{end}}
例子1: map类型模版参数:
//假设传入的模版参数定义如下
dataMap := map[string]string{}
dataMap["key1"] = "value1"
dataMap["key2"] = "value2"
模版代码
{{range $key, $value := .}}
key: {{$key}}
value: {{$value}}
{{end}}
输出:
key: key1
value: value1
key: key2
value: value2
例子2:
假设传入的模版参数如下, 这里传入一个struct类型:
type Data struct {
Titles []string
}
//初始化模版参数
data := Data{Titles:[]string{"标题1", "标题2", "标题3"}}
模版代码
{{range $index, $value := .Titles}}
{{$index}} - {{$value}}
{{end}}
输出:
0 - 标题1
1 - 标题2
2 - 标题3
3. with语句
with语句主要用于struct类型数据的访问,就是一种快速访问struct数据的方式。
语法:
{{with struct类型对象}}
{{.字段}}
{{end}}
在with语句中,点( . ) 代表的是对with引用的struct对象,而不是模版参数。
例子:
//定义用户类型
type User struct {
Id int
UserName string
}
//模版参数类型
type Data struct {
User User
}
//定义user
user := User{Id:1001, UserName:"李大成"}
//初始化模版参数, 假设我们传入的模版参数是data
data := Data{}
data.User = user
模版代码:
{{with .User}}
Id: {{.Id}}
Username: {{.UserName}}
{{end}}
输出:
Id: 1001
Username: 李大成
注意: range和with语句都改变了点(.)引用的数据,那么如果想要在range和with语句中引用模版参数,请先将(点(.)赋值给一个自定义变量, 然后在range和with中通过自定义变量,引用模版参数。
Golang 模版函数
模板引擎为我们提供了函数机制,方面我们在处理模板时执行一些特定的功能,例如格式化输出内容、字母大小写转换等等。
1.函数调用语法
语法格式:
functionName [Argument...]
Argument参数是可选的,如果有多个参数,参数直接用空格分隔。
例子:
{{html "<h1>www.tizi365.com</h1>"}}
输出:
<h1>www.tizi365.com</h1>
html函数的作用是对输入参数进行html转义处理,html标签都转义了。
多个函数参数的例子:
{{printf "%d - %s" 100 "www.tizi365.com"}}
printf函数主要用于格式化输出字符串,是fmt.Sprintf函数的别名,用法跟fmt.Sprintf函数一样,区别就是模板函数的参数用空格隔开, 这里为printf输入了3个参数。 下面是输出:
100 - www.tizi365.com
2.内置模板函数
模板引擎预先定义了一些函数,我们可以直接在模板中进行函数调用,下面介绍常用的内置函数。
2.1. 关系运算和逻辑运算函数
在模板引擎中关系运算(==、<、<=、>、>=、!=)和逻辑运算(and、or、not)都封装成函数形式,也就是说我们需要通过函数调用的方式进行关系运算和逻辑运算。
下面是关系运算函数:
说明:下表中的arg1和arg2代表两个参数
函数名 | 函数调用格式 | 对应关系运算 | 说明 |
---|---|---|---|
eq | eq arg1 arg2 | arg1 == arg2 | arg1等于arg2则返回true |
ne | ne arg1 arg2 | arg1 != arg2 | arg1不等于arg2则返回true |
lt | lt arg1 arg2 | arg1 < arg2 | arg1小于arg2则返回true |
le | le arg1 arg2 | arg1 <= arg2 | arg1小于等于arg2则返回true |
gt | gt arg1 arg2 | arg1 > arg2 | arg1大于arg2则返回true |
ge | ge arg1 arg2 | arg1 >= arg2 | arg1大于等于arg2则返回true |
下面是逻辑运算函数:
函数名 | 函数调用格式 | 对应逻辑运算 | 说明 |
---|---|---|---|
and | and 表达式1 表达式2 | 表达式1 && 表达式2 | 表达式1和表达式2都为真的时候返回true |
or | or 表达式1 表达式2 | 表达式1 || 表达式2 | 表达式1和表达式2其中一个为真的时候返回true |
not | not 表达式 | !表达式 | 表达式为false则返回true, 反之返回false |
提示: 关系运算和逻辑运算函数通常跟if语句一起使用
例子:
{{$x := 100}}
//等价于$x == 100
{{if eq $x 100}}
...代码...
{{end}}
//等价于$x < 100
{{if lt $x 500}}
...代码...
{{end}}
//等价于$x >= 100
{{if ge $x 500}}
...代码...
{{end}}
//等价于$x > 50 && $x < 200
//这里调用了and函数和gt、lt三个函数, gt和lt函数的结果作为and的参数,gt和lt函数调用分别用括号包括起来
{{if and (gt $x 50) (lt $x 200)}}
...代码...
{{end}}
{{$y := 200}}
//等价于$x > 100 || $y > 100
{{if or (gt $x 100) (gt $y 100)}}
...代码...
{{end}}
2.2. html函数
对html内容进行转义处理,前面的例子已经介绍。
2.3. len函数
用于计算数组大小。 例子:
数组大小: {{len .}}
假如传入的模板参数是一个数组:
//模板参数定义如下
a := []int{1,2,3,4}
输出:
数组大小: 4
2.4. printf函数
主要用于格式化字符串,是go fmt.Sprintf函数的别名,前面的例子已经介绍。
2.5. urlquery函数
主要用于url编码。 例子:
/search?keyword={{urlquery "搜索关键词"}}
输出:
/search?keyword=%E6%90%9C%E7%B4%A2%E5%85%B3%E9%94%AE%E8%AF%8D
3.pipeline
pipeline 翻译过来可以称为管道或者流水线, pipeline运算的作用是将多个函数调用或者值串起来,从左往右执行,左边执行的结果会传递给右边,形成一个任务流水。
pipeline运算符:| (竖线)
语法格式:
command1 | command2 | command3 ...
command可以是一个值,也可以是一个函数。
例子1:
{{"<h1>www.tizi365.com</h1>" | html}}
这里意思就是将第一个字符串值传递给html函数。
输出:
<h1>www.tizi365.com</h1>
例子2:
{{"关键词" | html | urlquery}}
这个例子就是先将 "关键词" 传递给html函数转义下html标签,然后在将html执行结果传递给urlquery函数进行url编码。
输出:
%E5%85%B3%E9%94%AE%E8%AF%8D
提示: 如果函数有多个参数,pipeline运算会将值传递给函数的最后一个参数, 例如: {{100 | printf "value=%d"}}, 这里将100传递给printf函数的最后一个参数。
4.自定义模板函数
内置的模板函数使用有限,我们可以自己定义模板函数。
下面代码展示如何自定义模板函数:
// 第一步,我们先创建FuncMap, 然后注册我们要定义的函数
// FuncMap是一个map类型
funcMap := template.FuncMap{
// "toupper" 就是我们在模板中可以调用的函数名,
// strings.ToUpper就是我们要注册的函数,
// 他的作用是将小写字母转成大写字母,这里我们直接将golang自带的函数注入进去。
// 当然你也可以自己写一个函数
"toupper": strings.ToUpper,
}
// 这里定义个模板代码
const templateText = `
自定义函数调用例子:{{"abcdef" | toupper}}
`
// 创建一个template对象,模板名字为test,
// 然后调用Funcs注册我们定义的模板函数,
// 然后调用Parse加载templateText模板代码。
tmpl, err := template.New("test").Funcs(funcMap).Parse(templateText)
if err != nil {
log.Fatalf("解析模板失败: %s", err)
}
// 渲染模板
err = tmpl.Execute(os.Stdout, "")
if err != nil {
log.Fatalf("渲染模板失败: %s", err)
}
输出:
自定义函数调用例子:ABCDEF
Go 模板引擎子模版用法
在实际项目中,我们不可能只有一个模板,一般来说都有很多个模板,而且这些模板也会共享一些公共的模板,这些公共的模板我们都可以定义成子模板,在需要的时候调用子模板,就可以将子模板的内容嵌入当前模板中。
提示:在项目中使用子模板,可以让项目模板具有模块化的能力,提高模块复用能力和可维护性。
1.定义子模板
定义子模板语法:
{{define "子模板名字"}}
模板内容
{{end}}
例子:
{{define "T1"}}
模板内容T1
{{end}}
{{define "T2"}}
模板内容T2
{{end}}
2.调用子模板
调用子模板语法:
{{template "子模板名字" 参数}}
template函数的第一个参数是模板名字,第二个参数是模板参数, 在子模板内部也是通过点( . ),引用模板参数。
提示: template的第二个模板参数是可选的。
例子:
//模板代码
//这里定义了T1 T2 T3三个模板,T3调用了T1和T2的模板内容,最后我们调用T3模板内容
const templateText = `{{define "T1"}}ONE{{end}}
{{define "T2"}}TWO{{end}}
{{define "T3"}}{{template "T1"}} {{template "T2"}}{{end}}
{{template "T3"}}`
// 创建一个template对象,模板名字为test,然后调用Parse加载templateText模板代码。
tmpl, err := template.New("test").Parse(templateText)
if err != nil {
log.Fatalf("解析模板失败: %s", err)
}
// 渲染模板
err = tmpl.Execute(os.Stdout, "")
if err != nil {
log.Fatalf("渲染模板失败: %s", err)
}
输出:
ONE TWO
3.模板管理
上面的例子,我们将模板代码定义在一个变量或者常量中,这个只是用于演示,实际项目中模板代码通常非常多,建议大家按如下方式组织模板代码:
-
一个模块的模板代码,保存在一个模板文件中,模板文件名为tpl。
-
所有的模板代码都定义在子模板中,方便根据模板名字进行渲染。
例子: 模板目录views, 下面分别按功能模块创建不同的模板文件
创建公共模板文件: views/common.tpl 主要用于保存一些公共的模板定义
{{define "common1"}}
这里是共享模块1
{{end}}
{{define "common2"}}
这里是共享模块2
{{end}}
创建mod1模块的模板文件: views/mod1.tpl
{{define "mod1"}}
这里是模块1
{{- template "common1"}}
{{end}}
创建mod2模块的模板文件: views/mod2.tpl
{{define "mod2"}}
这里是模块2
{{- template "common2"}}
{{end}}
渲染模板代码:
//创建template对象,并且加载views目录下面所有的tpl模板文件。
t := template.Must(template.ParseGlob("views/*.tpl"))
// 渲染mod1子模板
t.ExecuteTemplate(os.Stdout, "mod1", nil)
// 渲染mod2子模板
t.ExecuteTemplate(os.Stdout, "mod2", nil)
输出:
这里是模块1
这里是共享模块1
这里是模块2
这里是共享模块2