Golang模板引擎快速入门教程

 

Go语言内置了 text/template 和 html/template两个模板库,专门用于处理网页html模板。 html/template 是在 text/template 模板库的基础上增加了对html输出的安全处理,主要目的是为了防止被攻击。

下面通过一个例子介绍template的用法。

模版引擎使用流程:

  1. 编写模版代码

  2. 导入包

  3. 加载模版代码

  4. 根据模版参数渲染模版

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
posted on 2022-08-10 13:56  root-123  阅读(982)  评论(0编辑  收藏  举报