Go语言的标准库提供了text/template和html/template两个模板引擎,其中html/template会自动处理HTML转义,更适合Web开发场景。在实际项目中,我们往往需要处理多个页面模板,同时复用导航栏、页脚等公共布局部分,这就需要我们掌握多模板渲染与布局管理的正确方法。

基础模板定义与渲染
首先我们来看最基础的单个模板渲染流程,需要先解析模板文件,再传入数据执行渲染。以下是一个简单的示例:
package main
import (
"html/template"
"net/http"
)
func handleIndex(w http.ResponseWriter, r *http.Request) {
// 解析单个模板文件
tpl, err := template.ParseFiles("templates/index.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 定义渲染数据
data := map[string]string{
"Title": "首页",
"Content": "欢迎来到我的网站",
}
// 执行模板渲染
err = tpl.Execute(w, data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func main() {
http.HandleFunc("/", handleIndex)
http.ListenAndServe(":8080", nil)
}
对应的index.html模板内容如下,注意模板中的变量需要用双花括号包裹:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{.Title}}</title>
</head>
<body>
<h1>{{.Content}}</h1>
</body>
</html>
公共布局模板的定义
当项目有多个页面时,页头、导航栏、页脚等部分通常是相同的,我们可以把这些公共部分提取到布局模板中。首先创建一个基础布局文件base.html:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>{{template "title" .}}</title>
</head>
<body>
<header>
<h1>我的网站</h1>
<nav>
<a href="/">首页</a>
<a href="/about">关于</a>
</nav>
</header>
<main>
{{template "content" .}}
</main>
<footer>
<p>版权所有 © 我的网站</p>
</footer>
</body>
</html>
这里使用了{{template "title" .}}和{{template "content" .}}语法,表示会插入名为title和content的子模板内容,后面的点表示把当前数据传递给子模板。
子模板与布局嵌套
接下来我们创建子模板,通过{{define "模板名"}}语法定义子模板内容,然后和布局模板一起解析。以关于页面为例,创建about.html:
{{define "title"}}关于我们{{end}}
{{define "content"}}
<h2>关于我们</h2>
<p>这是关于我们的页面内容,这里可以介绍网站的相关信息。</p>
{{end}}
然后修改处理函数,同时解析布局模板和子模板:
func handleAbout(w http.ResponseWriter, r *http.Request) {
// 同时解析布局模板和子模板,注意顺序:布局模板在前,子模板在后
tpl, err := template.ParseFiles("templates/base.html", "templates/about.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
data := map[string]string{
"PageName": "about",
}
// 执行布局模板,子模板会自动嵌入到对应位置
err = tpl.ExecuteTemplate(w, "base.html", data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
这里需要注意,ParseFiles会按照传入的顺序解析模板,布局模板中的template指令会匹配后面解析的子模板中定义的同名模板块。执行渲染时要指定执行的是布局模板base.html,子模板会自动生效。
多模板批量管理与缓存
当项目模板数量较多时,每次请求都解析模板会影响性能,我们可以把模板解析放到程序初始化阶段,实现模板缓存。以下是一个通用的模板管理方案:
package main
import (
"html/template"
"net/http"
"path/filepath"
)
// 定义模板缓存变量
var tplCache *template.Template
func initTemplates() error {
// 匹配所有模板文件,假设所有模板都放在templates目录下,子模板以.html结尾
// 布局模板是base.html,其他.html文件是子模板
pattern := filepath.Join("templates", "*.html")
files, err := filepath.Glob(pattern)
if err != nil {
return err
}
// 解析所有匹配的模板文件
tplCache, err = template.ParseFiles(files...)
if err != nil {
return err
}
return nil
}
func renderTemplate(w http.ResponseWriter, tplName string, data interface{}) {
// 渲染指定名称的模板
err := tplCache.ExecuteTemplate(w, tplName, data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func handleIndex(w http.ResponseWriter, r *http.Request) {
data := map[string]string{
"Title": "首页",
"Content": "欢迎来到我的网站",
}
renderTemplate(w, "base.html", data)
}
func handleAbout(w http.ResponseWriter, r *http.Request) {
data := map[string]string{
"Title": "关于我们",
}
renderTemplate(w, "base.html", data)
}
func main() {
// 初始化模板缓存
err := initTemplates()
if err != nil {
panic(err)
}
http.HandleFunc("/", handleIndex)
http.HandleFunc("/about", handleAbout)
http.ListenAndServe(":8080", nil)
}
这个方案中,程序启动时就会解析所有模板文件并缓存到tplCache变量中,后续请求直接使用缓存的模板,不需要重复解析,提升了性能。
自定义模板函数
Go模板支持自定义函数,我们可以注册自定义函数来处理一些特殊的数据格式化需求。例如注册一个格式化时间的函数:
func initTemplates() error {
// 定义自定义函数映射
funcMap := template.FuncMap{
"formatTime": func(t int64) string {
// 简单的时间格式化示例,实际可以根据需求调整
return time.Unix(t, 0).Format("2006-01-02 15:04:05")
},
}
pattern := filepath.Join("templates", "*.html")
files, err := filepath.Glob(pattern)
if err != nil {
return err
}
// 创建模板时传入自定义函数映射
tplCache = template.New("").Funcs(funcMap)
// 解析所有模板文件
tplCache, err = tplCache.ParseFiles(files...)
if err != nil {
return err
}
return nil
}
在模板中就可以直接使用这个自定义函数:
{{define "content"}}
<p>发布时间:{{formatTime .PublishTime}}</p>
{{end}}
注意事项
- 使用html/template时,所有传入的动态内容都会自动进行HTML转义,避免XSS攻击,如果需要输出不转义的HTML内容,需要使用
template.HTML类型包装数据。 - 模板嵌套时,子模板的define块名要和布局模板中template指令引用的名称一致,否则无法正确嵌入。
- 批量解析模板时,如果多个模板文件定义了同名的模板块,后解析的文件会覆盖先解析的,需要注意文件顺序。
- 生产环境中建议开启模板缓存,避免每次请求都解析模板带来的性能损耗。