Go 视图模板篇(一):模板引擎的定义、解析与执行
模板和模板引擎
在 Web 编程中,模板引擎用于聚合数据和模板并生成最终的 HTML 文档,处理器调用模板引擎来完成这一工作并将 HTML 文档作为响应实体发送给客户端:
虽然模板引擎没有统一的标准,甚至不同的模板引擎提供的功能特性也是天差地别,但是仍然可以划分为两种不同的类型:
- 无业务逻辑:数据通过指定占位符替换,模板中不包含业务逻辑,所有业务逻辑都在处理器中完成,这样做的好处是将业务逻辑和数据渲染很好的隔离开。
- 嵌入业务逻辑:在视图模板中嵌入业务逻辑,这使得视图模板的功能非常强大,但是这样一来,也使得代码维护非常困难。
我们倾向于无业务逻辑嵌入的模板引擎,这样的视图模板性能更好,可维护性更好,但是绝对的无业务逻辑嵌入也是做不到的(比如一些简单的条件判断和循环),大部分时候这取决于业务开发团队的约定,尽量不要在视图模板中编写业务逻辑代码。
PHP 诞生之初就是一个将业务逻辑和 HTML 视图混为一体的脚本语言,不过现在的 PHP 脚本中已经很少看到 HTML 代码了,这是 PHP 框架的功劳,比如 Laravel、Yii,PHP 自身作为一个模板引擎,现在已经衍生出独立的模板引擎,比如 Smarty、Blade。
实际上,大部分模板引擎都是介于以上两种类型之间,只能说离谁更近一些。
Go 语言官方提供的模板引擎 text/template
和 html/template
也是这样的混合物。
Go 模板引擎
Go 模板引擎都是在处理器中触发,指定要解析的模板文件,并传入待渲染的数据,最后返回由模板引擎最终生成的 HTML 作为 HTTP 响应发送给客户端:
Go 模板都是文本文档,在 Web 应用中,通常是 HTML 文档,其中包含了嵌入的命令。这些文档会被 Go 模板引擎解析和执行,生成另外的文本片段(替换完命令和数据)。
Go 标准库提供了 text/template 库用于解析任意类型的文本格式模板,以及 html/template 库用于解析并处理 HTML 格式模板。
在这些模板中,命令以 {{
和 }}
包裹(实际上,这些界定符可以通过程序进行修改),下面我们看一段简单的模板代码 tmpl.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Go Web Programming</title>
</head>
<body>
{{ . }}
</body>
</html>
模板文件中的内容必须是可读的文本格式,但是扩展名可以随意,在这里由于最终生成的是 HTML 文档,所以我们使用了 .html
扩展名(tmpl.html
)。
{{ . }}
中的 .
就是一个命令,用于在模板执行时替换从处理器传入的变量。
使用 Go 模板引擎通常包括以下两个步骤:
- 解析文本模板源,可以是表单字符串、或者模板文件,用于创建解析后的模板结构体。
- 执行解析后的模板,传递
ResponseWriter
和变量数据,这样一来,模板引擎就可以基于模板和数据生成最终的 HTML 并将其传递给ResponseWriter
发送给客户端。
下面是服务端处理器调用模版引擎渲染上述模板代码的示例:
package main
import (
"html/template"
"net/http"
)
func process(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("tmpl.html") // 解析模板,返回 Template
t.Execute(w, "Hello World!") // 执行模板,并将其传递给 w
}
func main() {
http.HandleFunc("/template", process)
http.ListenAndServe(":8080", nil)
}
运行上述代码启动服务器,在终端窗口通过 curl 请求 /template
路由,返回结果如下:
表明 HTML 模板解析渲染成功,对应的 {{ . }}
也别替换成 Hello World!
。
解析模板
在上面的示例代码中,我们调用了 ParseFiles
方法解析模板文件并创建稍后执行的解析后的 Template。其底层分为两步,它可以接收一个或多个模板文件名称,传入多个模板文件名的时候,会以第一个文件名作为模板名称,后续其它模板通常是第一个模板或者其他模板嵌套的子模板。
此外,我们还可以通过 ParseGlob
方法解析模板,该方法传入的参数是模式匹配串,而不是文件名称:
t, _ := template.ParseFiles("tmpl.html")
t, _ := template.ParseGlob("*.html")
如果当前路径只有一个 tmpl.html
模板文件,上述代码的效果是一样的。
除了解析文件之外,还支持解析字符串,实际上,所有解析方法最终调用的都是 Parse
方法:
package main
import (
"html/template"
"net/http"
)
func parseFiles(w http.ResponseWriter, r *http.Request) {
t, _ := template.ParseFiles("tmpl.html")
t.Execute(w, "Hello World!")
}
func parseString(w http.ResponseWriter, r *http.Request) {
tmpl := `<!DOCTYPE html> <html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Go Web Programming</title>
</head>
<body>
{{ . }}
</body>
</html>`
t := template.New("tmpl.html")
t.Parse(tmpl)
t.Execute(w, "Hello World!")
}
func main() {
http.HandleFunc("/template", parseFiles)
http.HandleFunc("/string", parseFiles)
http.ListenAndServe(":8080", nil)
}
上面 parseString
方法和 parseFiles
方法实现的效果是一样的,实际上 template.ParseFiles("tmpl.html")
底层调用的代码正是:
t := template.New(filename)
t.Parse(string) // string 就是读取传入 file 的文本内容
在上面的代码中,我们忽略了 template.ParseFiles
返回的错误信息,不过,Go 官方建议我们对这个错误进行处理,为此,Go 还提供了更简洁的方式来处理模板解析过程中出现的错误:
t := template.Must(template.ParseFiles("tmpl.html"))
这种情况下,如果解析模板过程中出现问题,则抛出 panic(在 Go 语言中,panic 有点类似其它语言的异常,当函数内抛出 panic 时,会一直上溯到 main 入口,然后崩溃)。
执行模板
如果只解析一个模板文件的话,使用 Execute
方法就够了,如果要解析多个模板文件,也可以使用 Execute
方法,这个时候,会使用传入模板文件的第一个作为模板名称,并将其作为入口模板,如果要指定其它模板作为入口模板(或者称之为布局模板),需要调用 ExecuteTemplate
方法并将模板名作为第二个参数传递进去:
t, _ := template.ParseFiles("t1.html", "t2.html")
t.Execute(w, "Hello World!")
t.ExecuteTemplate(w, "t1.html", "Hello World!")
上面的 t.Execute
和 t.ExecuteTemplate
执行结果等效,如果你要从 t2.html
开始解析,需要这样指定入口文件:
t.ExecuteTemplate(w, "t2.html", "Hello World!")
No Comments