这是我参与8月更文挑战的第28天,活动详情查看:8月更文挑战
文章内容来自本人学习《Go Web编程实战派》及自己的实践总结
模版引擎
Go语言中通用的模版引擎库text/template用于处理任意格式的文本。另外,Go语言还单独提供了html/template包,用于生成可对抗代码注入的安全HTML文档
模板原理
模板和模板引擎
在给予MVC模型的Web架构中,我们常将不变的部分提出来成为模版,而那些可变部分有后端程序提供数据,借助模版引擎渲染来生成动态网页
模板可以被理解为事先定义好的HTML文档。模板渲染可以被简单理解为文本替换操作——是后端用相应的数据去替换HTML文档中实现准备好的标记
模板的诞生是为了将显示于数据分离(即前后端分离);模板技术多种多样,但本质是将模板文件和数据通过模板引擎生成最终的HTML文档。模版引擎有很多,比如:Node.js的ejs、nunjucks等等
Go语言的模板引擎
Go语言内置了文本模板引擎text/template包,以及用于生成HTML文档的html/template包,它们的使用基本类似,大致总结为以下几点:
- 模板文件的后缀通常是
.tmpl和.tpl(也可以使用其他的后缀),必须使用UTF-8编码 - 模板文件中使用
{{}}来包裹和表示需要传入的数据 - 传给模板的数据可以通过点号
.来访问。如果是符合类型的数据,则可以通过{{.FieldName}}来访问它的字段 - 出
{{}}包裹外,其他的内容均不做任何处理,原样输出
Go语言模版引擎的使用分为:定义模板文件、解析模板文件和渲染文件
-
定义模板文件
定义模板文件是指按照相应的语法规则去定义模板文件
-
解析模板文件
html/template包提供了以下方法来解析模板文件、获取模板对象;可以通过New()函数来创建模板对象,并为其挺假一个模板名称。New()函数的定义如下:func New(name string) *Template可以使用
Parse()函数来创建模板对象并完成解析模版内容。Parse()定义方法如下:func (t *Template) Parse(src string) (*Template, err)如果要解析模板文件,则可以使用
ParseFile()函数,该函数会返回模板对象。该函数定义如下:func ParseFiles(filenames ...string) (*Template, error)如果要批量解析文件,则可以使用
ParseGlob()函数。该函数的定义如下:func ParseGlob(pattern string) (*Template, error)可以使用
ParseGlob()函数来进行正则匹配,比如在当前解析目录下有以a开头的模板文件,则使用template.ParseGlob("a*") -
渲染模板文件
html/template包提供了Execute()和ExecteTemplate()方法来渲染模板func (t *template) Execute(wr Writer, data interface{}) error {} func (t *template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {}在创建
New()函数时就为模板对象添加了一个模板名称,执行`Execute()方法后会默认去寻找该名称进行数据融合使用
ParseFile()函数可以一次加载多个模板,此时不可以使用Execute()来执行数据融合,可以通过ExecuteTemplate()方法指定模板名称来执行数据融合
使用html/template包
创建模板
在Go语言中,可以通过将模板应用于一个数据结构(即把该数据结构作为模板的参数)来执行并输出HTML文档
模板在执行时会遍历数据结构,并将指针指向运行中的数据结构中的.的当前位置
用作模板的输入文本必须是UTF-8编码的文本;Action是数据运算和控制单位,Action有{{和}}界定;在Action之外的所有文本都不做修改的复制到输出中。Action内部不能有换行,但注释可以有换行
接下来就创建并实践模板吧
-
新建一个项目example,然后在example中分别创建examp/template/index.tmpl和main.go
-
创建模板文件
// example/template/index.tmpl <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>模板</title> </head> <body> <p>加油,少年!{{.}}</p> </body> </html> -
创建用于创建和渲染模板的文件(main.go)
package main import ( "fmt" "html/template" "net/http" ) func helloHandleFunc(w http.ResponseWriter, r *http.Request) { // 解析模板 tpl, err := template.ParseFiles("./template/index.tmpl") if err != nil { fmt.Println("template parsefile failed, err:", err) return } // 渲染模版 name := "learn Go" tpl.Execute(w, name) } func main() { http.HandleFunc("/", helloHandleFunc) http.ListenAndServe(":8082", nil) } -
在命令行终端输入启动命令(注意,一定是在文件所在路径下)
go run main.go -
在浏览器中访问
http://localhost:8082/,最终效果图如下所示:
Go语言模板语法
模板语法都包含在{{和}}中间,其中.表示当前对象,在传入一个结构体对象时,可以根据.来访问结构体的对应字段,例如来写一个个人信息的模板渲染:
// example/template/user.tpml
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>模板</title>
</head>
<body>
<table border="1">
<thead>
<th rowspan="2">个人信息</th>
</thead>
<tr>
<td>nickname</td>
<td>{{.Nickname}}</td>
</tr>
<tr>
<td>age</td>
<td>{{.Age}}</td>
</tr>
<tr>
<td>gender</td>
<td>{{.Gender}}</td>
</tr>
<tr>
<td>email</td>
<td>{{.Email}}</td>
</tr>
</table>
</body>
</html>
完善解析模板的文件及数据
// example/main.go
type User struct {
Nickname string
Gender string
Age int
Email string
}
func handleSayHi(w http.ResponseWriter, r *http.Request){
// 解析模板
tpl, err := template.ParseFiles("./template/user.tmpl")
if err != nil {
fmt.Println("create template failed, err:", err)
return
}
// 利用给定数据渲染模版,并将结果写入w
user := User{
Nickname: "Forest",
Gender: "man",
Age: 22,
Email: "767425412@qq.com",
}
tpl.Execute(w, user)
}
重新运行命令启动程序,然后在浏览器中访问:http://localhost:8082/say-hi,效果图如下:
在传入的变量时map时,也可以在模板文件中通过
.来访问值
常用模板语法
-
注释
在Go语言中,HTML模板的注释结构如下:
{{/* 这是一个注释,不会被解析 */}} -
管道(pipeline)
管道是指产生数据的操作;比如:
{{.}}、{{.Nickname}}等,Go的模板语法中支持用管道符|来链接多个命令,用法和UNIX下的管道类似:|前面的命令会将晕眩结果(或返回值)传递给后一个命令的最后一个位置::: tip 注意
并不是只有使用
|才是管道,在Go的模板语法中,pipeline的概念时传递数据,只要能产生数据的结构,都是pipeline:::
-
变量
在Action里可以初始化一个变量来捕获管道的执行结果。初始化语法如下:
$variable := pipline其中
$variable是变量的名字。声明变量的Action不会产生任何输出 -
条件判断
Go模板语法中的条件判断有一下几种:
{{if pipline}} T1 {{end}} {{if pipline}} T1 {{else}} T0 {{end}} {{if pipline}} T1 {{else if pipline}} T0 {{end}} -
renge关键字
在Go的模板语法中,使用
range关键字进行遍历,其中pipline的值必须是数组、切片、map或者channel。其语法以{{range pipline}}开头,以{{end}}结尾,形式如下:{{range pipline}} T1 {{end}}如果
pipline的值值其长度为0,则不会有任何输出。中间也可以有{{else}},如下:{{range pipline}} T1 {{else}} T0 {{end}}如果
pipline的值其长度为0则会执行T0示例如下:
package main import ( "html/template" "log" "net/http" ) func handleRange(w http.ResponseWriter, r *http.Request) { rangeTemplate := `<p>{{if .Kind}}</p> <p>{{range $i, $v := .MapContent}}</p> <p>{{$i}} => {{$v}},{{$.OutsideContent}}</p> <p>{{end}}</p> <p>{{else}}</p> <p>{{range .MapContent}}</p> <p>{{.}}, {{$.OutsideContent}}</p> <p>{{end}}</p> <p>{{end}}</p>` str1 := []string{"first range", "use index and value"} str2 := []string{"second range", "not use index and value"} type Content struct { MapContent []string OutsideContent string Kind bool } var content = []Content{ {str1, "第一次外面的内容", true}, {str2, "第二次外面的内容", false}, } // 创建模板并将字符解析进去 t := template.Must(template.New("range").Parse(rangeTemplate)) fmt.Println("t:", t) for _, c := range content { err := t.Execute(w, c) if err != nil { log.Println("executing template:", err) } } } func main() { http.HandleFunc("/range", handleRange) http.ListenAndServe(":8082", nil) }重新运行程序后,访问浏览器效果图如下:
-
with关键字
在Go的模板语法中,
with关键字和if关键字有点类似,{{with}}操作尽在传递的管道部位空时有条件地执行其主体。如下:{{with pipline}} T1 {end}如果
pipline为空,则不产生输出。中间也可以{{else}};例如:{{with pipline}} T1 {{else}} T0 {{end}}如果
pipline为空,则不改变.并执行T0;否则将.设为pipline的值并执行T1 -
比较函数
布尔函数会将任何类型的零值是为假,将其余视为真。例如下面常用的二元比较运算符:
运算符 说明 eq 相等判断;例如:arg1 == arg2 ne 不相等判断;例如:arg1 != arg2 lt 小于判断;例如:arg1 < arg2 le 小于等于判断;例如:arg1 <= arg2 gt 大于判断;例如:arg1 > arg2 ge 大于等于判断;例如:arg1 >= arg2 为简化多参数相等检测,
eq可以接受2个或者多个参数,它将第一个参数和其余参数依次比较,如下:{{eq arg1 arg2 arg3 ... argn}}即只能做如下比较
arg1 ==arg2 || arg1 == arg2 || ... || arg1 == argn比较函数只适用基本类型(或在重定义的基本类型,如"type Balance float32")。但整数和浮点数不能互相比较
-
预定义函数
预定义函数是模板库中定义好的函数,可以直接在
{{}}中使用,预定义函数名及其功能见下表:函数名 说明 and 函数返回其第1个空参数或者最后一个参数,即: "and x y"等价于"if x then y else x"。所有参数都会执行or 返回第一个非空参数或者最后一个参数,即 "or x y"等价于"if x then x else y"。所有参数都会执行not 返回其单个参数的布尔值“不是” len 返回其参数的整数类型长度 index 执行结果为 index()函数后第一个参数以第一个参数后面剩下的参数为索引指向的位置。每个被索引的主体必须是数组、切片、mapprint 即 fmt.Sprintprintf 即 fmt.Sprintfprintln 即 fmt.Sprintlnhtml 返回其参数文本表示的HTML逸码等价表示 urlquery 返回其参数文本表示的可嵌入URL查询的逸码等价表示 js 返回其参数文本表示的JavaScript译码等价表示 call 执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数 -
自定义函数
Go语言的模板支持自定义函数。自定义函数通过调用
Funcs()方法实现,其定义如下:func (t *Template) Funcs(funcMap FuncMap) *TemplateFuncs()方法向模板对象的函数字典里加入参数funcMap内的键值对。如果funcMap的某个键值对的值不是函数类型,或者返回值不符合要求,则会报panic错误,但可以对模板对象的函数列表的成员进行重写。方法返回模板对象以便进行链式调用。FuncMap的定义如下:type FuncMap map[string]interface{}FuncMap类型定义了函数名字符串到函数的映射,每个函数都必须有1个或者2个返回值。如果有两个返回值,则后一个必须是error接口类型;如果有2个返回值的方法返回error非nil,则模板执行会中断并返回该错误给调用者在执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模版内定义函数,而是使用
Funcs()方法添加函数到模板里。