go template使用
以text/template为例, 而html/template的接口与前者一样,不再缀述。
模板文件一般由.tmpl
或.tpl
为后缀。
一些名词
dot:用表示
.
,相当于一个变量,保存着传进来的值,可以改变
pipeline:从字面上看,有点像管道|
,但从文档上看,实际上指的是一切取值操作,包括{{ . }}
、{{ $name }}
,而|
与unix中的一样:作为函数的最后一个参数
{{ }}:相当于占位符,主要的逻辑都写在里面
1 模板定义#
1.1 取值#
取值的作用主要是在页面中表示出来,或者使用一个变量保存
类型 | 方式 | 解释 |
---|---|---|
当前值 | {{ . }} |
传什么值,就取什么值,假如直接在页面上输出的话,类似fmt.Println |
结构体 | {{ .Field }} |
Field 指的是字段名,假如结构体嵌套,还可以再使用. 取值,注意:遵循可见性规则 |
变量 | {{ $varName }} |
以$ 开头,取出变量的值,如何定义且看1.2 |
字典 | {{ .key }} |
取字典key对应的值,不需要首字母大写,嵌套时,可以再使用. 取值 |
无参数方法 | {{ .Method }} |
执行Method这个方法,第一个返回值作为取出的值,注意:遵循可见性规则,而且返回值有要求,详细见xxx |
无参数函数 | {{ func }} |
执行func(),把返回值当做结果,详见xxx |
1.2 变量#
有些值,我们可能需要重复使用,最好的方法就是使用一个变量来保存值减少重复求值的过程。
// 用到的数据 name := "abcdef"
假如我们把name传进来,那么假如要求其长度并将其保存起来,可以使用一个内置函数(见1.4):len
在go template中,用$
表示变量,有点类似shell,使用:
{{ $lenght := len . }} <h1>长度:{{ $lenght }}</h1>
实际上,还可以这样写:
{{ $lenght := . | len }} <h1>长度:{{ $lenght }}</h1>
利用|
把abcdef
当做最后一个参数传给len
1.3 动作#
go template的动作(action)有点像,django的模板引擎中的tag,不过两者之间还是有较大的不一样。
1.3.1 注释#
注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止,就像这里表示的一样。
{{/* 我是注释啊 */}}
1.3.2 if判断#
有以下3种
{{if pipeline}} T1 {{end}}
如果pipeline的值为empty,不产生输出,否则输出T1执行结果。不改变dot的值。
Empty值包括false、0、任意nil指针或者nil接口,任意长度为0的数组、切片、字典。
如:
<p>{{ if . }}welcome{{ end }}</p>
在这里我传的是一个bool值,为true,因此p便签中的内容为welcome
{{if pipeline}} T1 {{else}} T0 {{end}}
如果pipeline的值为empty,输出T0执行结果,否则输出T1执行结果。不改变dot的值。
<p>{{ if . }}welcome{{ else }} Get out!{{ end }}</p>
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
用于简化if-else链条,else action可以直接包含另一个if;等价于:
<p>{{ if eq . 1 }}count=1{{ else if eq . 2}} count=2{{ else if eq . 3}} count=3{{ end }}</p>
这里的eq
是一个内置函数,相当于==
。
1.3.3 with#
这里的with
与并不是Python的with。go template的with
相当于可以暂时修改dot的if。
形式一: {{with pipeline}} T1 {{end}}
如果pipeline为empty不产生输出,否则将dot设为pipeline的值并执行T1。不修改外面的dot。
{{ with gt . 18}} result:{{ . }}, 嘿嘿嘿 {{end}}
这里的gt相当于>
, 因此假如执行成功,那么.
必然是true
.
形式二:{{with pipeline}} T1 {{else}} T0 {{end}}
如果pipeline为empty,不改变dot并执行T0,否则dot设为pipeline的值并执行T1。不修改外面的dot。
实际上这和上面的一样,就是多了个{{ else }}
{{ with gt . 18}} result:{{ . }}, 嘿嘿嘿 {{else}} 才{{ . }}岁,未成年 {{end}}
1.3.4 遍历#
遍历的值必须是数组、切片、字典或者通道。
- 简单形式:
{{range pipeline}} T1 {{end}}
如果pipeline的值其长度为0,不会有任何输出;
否则dot依次设为数组、切片、字典或者通道的每一个成员元素并执行T1;
如果pipeline的值为字典,且键可排序的基本类型,元素也会按键的顺序排序。
如,要遍历的数据如下:
data := map[string]string{ "张三": "hello", "李四": "word", }
在模板文件中定义:
<div> {{ range . }} <p>{{ . }}</p> {{ end }} </div>
所得到的结果:
<div> <p>hello</p> <p>word</p> </div>
- 加else形式:
{{range pipeline}} T1 {{else}} T0 {{end}}
如果pipeline的值其长度为0,不改变dot的值并执行T0;否则会修改dot并执行T1。
假如数据是一个空切片[]int{}
:
<div> {{ range . }} <p>{{ . }}</p> {{ else }} <span>no data</span> {{ end }} </div>
结果是<span>no data</span>
1.3.5 嵌套与继承#
define
当解析模板时,可以定义另一个模板,该模板会和当前解析的模板相关联。模板必须定义在当前模板的最顶层,就像go程序里的全局变量一样。
这种定义模板的语法是将每一个子模板的声明放在"define"和"end" action内部。
如:
{{ define "rd"}} <div> {{ range . }} <p>{{ . }}</p> {{ else }} <span>v2 no data</span> {{ end }} </div> {{ end }}
注意:结尾{{ end }}
和define
后面的是字符串
template
template
就是对define
定义的模板或其他模板文件的引用。
template的形式
{{template "name"}}
执行名为name的模板,提供给模板的参数为nil,如模板不存在输出为""{{template "name" pipeline}}
执行名为name的模板,提供给模板的参数为pipeline的值。
如,在当前文件中引用:
{{ define "rd"}} <div> {{ range . }} <p>{{ . }}</p> {{ else }} <span>v2 no data</span> {{ end }} </div> {{ end }} <!DOCTYPE html> <html lang="zh-CN"> <head> <title>template</title> </head> <body> {{/*引用*/}} {{ template "rd"}} </body> </html>
template引用其他文件注意:
- 千万注意,要在代码中把文件读进来。
t, _ := template.ParseFiles("./h1.tpl", "./h2.tpl")
也可以使用其他函数 - template后面跟的是完整的文件名
在h1.tpl中:{{ template "h2.tpl" }}
{{template "name" pipeline}}
形式
就是把define中或文件中的.
替换成template传进去的值,假如不指定的话,使用当前文件的.
{{ define "say"}} <h1>say {{ . }}</h1> {{ end }} {{ template "say" "hi"}}
结果:<h1>say hi</h1>
block
block是定义模板{{define "name"}} T1 {{end}}
和执行{{template "name" pipeline}}
缩写,典型的用法是定义一组根模板,然后通过在其中重新定义块模板进行自定义。
如,在./templates/base.tpl
中,定义:
<!DOCTYPE html> <html lang="zh-CN"> <head> <title>Go Templates</title> </head> <body> <div class="container-fluid"> {{block "content" . }}{{end}} </div> </body> </html>
而在其他的模板文件中:
{{template "base.tpl"}} {{/* 使用 */}} {{define "content"}} <!-- 写入自己的代码 --> <div>Hello world!</div> {{end}}
同样要注意,在解析文件时把多个模板文件传进来
1.3.6 去空#
{{- . -}}
使用{{-语法去除模板内容左侧的所有空白符号, 使用-}}去除模板内容右侧的所有空白符号。
1.4 函数#
执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用Funcs方法添加函数到模板里。
1.4.1 一般函数#
- and
函数返回它的第一个empty参数或者最后一个参数;
就是说"and x y"等价于"if x then y else x";所有参数都会执行;
和上面一样:Empty值包括false、0、任意nil指针或者nil接口,任意长度为0的数组、切片、字典。下面再重复
如:
{{ and 1 0 }} {{/* 返回0 */}} {{ and 1 1 1}} {{/* 返回1 */}}
- or
返回第一个非empty参数或者最后一个参数;
亦即"or x y"等价于"if x then x else y";所有参数都会执行;
如:
{{ or 1 0 }} {{/* 返回1 */}} {{ or 0 2 1}} {{/* 返回2 */}}
- not
返回它的单个参数的布尔值的否定
如:
{{ not 1 }} {{/* 返回false */}} {{ not 0 }} {{/* 返回true */}}
- len
返回它的参数的整数类型长度
如:
{{/* . 为"abcdef" */}} {{ len . }} {{/* 返回6 */}}
- index
执行结果为第一个参数以剩下的参数为索引/键指向的值;
如"index x 1 2 3"返回x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典。
假如数据为:
data := [][]int{ {1, 2, 3, 4, 5,}, {6, 7, 8, 9, 10,}, }
{{ index . 0 1}} {{/* 结果为2 */
-
print
即fmt.Sprint
S系列函数会把传入的数据生成并返回一个字符串。以下两个相同。 -
printf
即fmt.Sprintf -
println
即fmt.Sprintln -
html
返回与其参数的文本表示形式等效的转义HTML。
这个函数在html/template
中不可用。 -
urlquery
以适合嵌入到网址查询中的形式返回其参数的文本表示的转义值。
这个函数在html/template
中不可用。 -
js
返回与其参数的文本表示形式等效的转义JavaScript。 -
call
执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数;
如{{ call .X.Y 1 2 }}
等价于go语言里的dot.X.Y(1, 2)
;
其中Y是函数类型的字段或者字典的值,或者其他类似情况;
call的第一个参数的执行结果必须是函数类型的值(和预定义函数如print明显不同);
该函数类型值必须有1到2个返回值,如果有2个则后一个必须是error接口类型;
如果有2个返回值的方法返回的error非nil,模板执行会中断并返回给调用模板执行者该错误;
1.4.2 布尔函数#
布尔函数会将任何类型的零值视为假,其余视为真。
函数 | 说明 |
---|---|
eq | 如果arg1 == arg2则返回真 |
ne | 如果arg1 != arg2则返回真 |
lt | 如果arg1 < arg2则返回真 |
le | 如果arg1 <= arg2则返回真 |
gt | 如果arg1 > arg2则返回真 |
ge | 如果arg1 >= arg2则返回真 |
注意:
为了简化多参数相等检测,eq(只有eq)可以接受2个或更多个参数,它会将第一个参数和其余参数依次比较,返回下式的结果:
arg1 == arg2 || arg1 ==arg3 || arg1==arg4 ...
(和go的||不一样,不做惰性运算,所有参数都会执行)
比较函数只适用于基本类型(或重定义的基本类型,如"type Celsius float32")。它们实现了go语言规则的值的比较,但具体的类型和大小会忽略掉,因此任意类型有符号整数值都可以互相比较;任意类型无符号整数值都可以互相比较;等等。但是,整数和浮点数不能互相比较。
1.4.3 自定义函数#
使用Funcs
方法,可以将自定义好的函数放入到模板中。
Funcs
的签名:
func (t *Template) Funcs(funcMap FuncMap) *Template
Funcs方法向模板t的函数字典里加入参数funcMap内的键值对。
如果funcMap某个键值对的值不是函数类型或者返回值不符合要求会panic。
但是,可以对t函数列表的成员进行重写。方法返回t以便进行链式调用。
例子:
例子中的使用的一些方法,见第2部分
// 1. 定义函数,首字母可以小写,注意返回值 func SayHi(char string) (string, error) { return "Hi" + char, nil } func indexFunc(w http.ResponseWriter, r *http.Request) { // 2. new t := template.New("hello.tpl") // 3. 加入t的函数列表,需要替换掉t t = t.Funcs(template.FuncMap{"sayHi": SayHi}) // 4. Parse 可以是文件也可以是字符串 t, _ = t.ParseFiles("./hello.tpl") userName := "xxx" // 5. 渲染 _ = t.Execute(w, userName) }
上面代码的2-4步,可以使用一段链式调用完成:
t, _ := template.New("hello.tpl").Funcs(template.FuncMap{"sayHi": SayHi}).ParseFiles("./hello.tpl")
注意事项
template.New
的文件名应该和要渲染的文件名一样
自定义函数有1-2个返回值,第一个值当做正式返回值。假如有第二个返回值:用来panic
,其类型必须是error
,当对应的值非nil
时,panic
2 一些常用的方法#
模板引擎的使用,一般有如下三步:
- 定义模板文件
- 解析模板文件
- 模板渲染
其中,第2、3步都要用到一些template的方法(这里用的是text/template)
2.1 解析模板文件的方法#
// 解析字符串 func (t *Template) Parse(src string) (*Template, error) // 解析1个或多个文件 func ParseFiles(filenames ...string) (*Template, error) // 解析用正则匹配到的文件 func ParseGlob(pattern string) (*Template, error)
使用#
1. Parse
这里使用New
函数:
func New(name string) *Template
其作用是创建一个名为name的模板。
t, _ := template.New("test.tpl").Parse("<h1>{{ . }}</h1>")
Parse可以多次调用,但只有第一次调用可以包含空格、注释和模板定义之外的文本。
如果后面的调用在解析后仍剩余文本会引发错误、返回nil且丢弃剩余文本;
如果解析得到的模板已有相关联的同名模板,会覆盖掉原模板。
2. ParseFiles
t, _ := template.ParseFiles("./h1.tpl", "./h2.tpl", "./h3.tpl")
解析匹配参数中的文件里的模板定义并将解析结果与t关联。
如果发生错误,会停止解析并返回nil,否则返回(t, nil)。至少要存在一个匹配的文件。
3. ParseGlob
t, _ := template.ParseGlob("./*.tpl")
解析当前目录下,所有以.tpl
结尾的文件,假如有专门的文件夹存放模板文件,可以使用templates/*.tmpl
(1层目录时)和templates/**/*.tmpl
(2层目录时)
匹配时,和ParseFiles
一样。
2.2 模板渲染的方法#
func (t *Template) Execute(wr io.Writer, data interface{}) error // Execute方法将解析好的模板应用到data上,并将输出写入wr。 // 如果执行时出现错误,会停止执行,但有可能已经写入wr部分数据。 // 模板可以安全的并发执行。 func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error // 类似Execute,但是使用名为name的t关联的模板产生输出。
Execute
渲染的是ParseFiles
或ParseGlob
得到的第一个文件,假如要读取多个文件时,就有可能渲染的不是想要的文件,所以需要使用ExecuteTemplate
指定一个已经解析的文件。
如:
t, _ := template.ParseFiles("./h1.tpl", "./h2.tpl") userName := "xxx" _ = t.Execute(w, userName)
怎么样都是渲染h1.tpl
,假如要渲染h2.tpl
:
t, _ := template.ParseFiles("./h1.tpl", "./h2.tpl") userName := "xxx" _ = t.ExecuteTemplate(w, "h2.tpl", userName)
注意
ExecuteTemplate
的name可以是define
的模块名
如:
t, _ := template.New("test").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`) _ = t.ExecuteTemplate(out, "T", "word")
当然,用其他解析方法也可以。
3 html/template的不同之处#
由于html/template
的API和text/template
的API是一样的,解析和渲染没有什么不一样,但是在定义模板时,考虑到网站的安全性,会对一些风险内容进行转义,因此会有text/template
有点差别。
如:
t, _ := template.New("test").Parse("{{ . }}") char := "<script>alert('you have been pwned')</script>!" _ = t.Execute(w, char)
得到的结果是:<script>alert('you have been pwned')</script>!
与预期不符,为此,html/template
有一个函数可以专门处理这些我们认为安全的字符串:template.HTML
。
再使用时,我们可以自定义一个safe
函数,和其他模板引擎一样,不对一些字符串转义。
func safe(s string) template.HTML { return template.HTML(s) }
然后使用:
t, _ := template.New("test").Funcs(template.FuncMap{"safe": safe}).Parse("{{ . | safe }}") _ = t.Execute(w, char)
补充#
如果{{.}
}是非字符串类型的值,可以用于JavaScript上下文环境里:
struct{A,B string}{ "foo", "bar" }
将该值应用在在转义后的模板里:
<script>var pair = {{.}};</script>
模板输出为:
<script>var pair = {"A": "foo", "B": "bar"};</script>
参考:
作者:忞翛
出处:https://www.cnblogs.com/lczmx/p/13268081.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
欢迎各位大佬评论交流。可以的话,不妨点一下推荐。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具