HTML 模板渲染

虽然现在前后端分离很流行,但很多场景下服务端渲染 HTML 仍然是刚需。Gin 对 Go 标准库的 html/template 做了封装,用起来很方便。

加载模板

首先需要告诉 Gin 你的模板文件在哪里:

package main

import (
    "net/http"
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    
    r.LoadHTMLGlob("templates/*")
    
    r.GET("/index", func(c *gin.Context) {
        c.HTML(http.StatusOK, "index.html", gin.H{
            "title": "首页",
            "message": "欢迎来到我的网站",
        })
    })
    
    r.Run(":8080")
}

LoadHTMLGlob 支持通配符,templates/* 会加载 templates 目录下所有文件。

模板目录结构

对于复杂项目,模板通常会分层组织:

templates/
├── layouts/
│   └── base.html
├── partials/
│   ├── header.html
│   └── footer.html
└── pages/
    ├── index.html
    └── about.html

加载时使用 ** 递归匹配:

r.LoadHTMLGlob("templates/**/*")

定义模板

在模板文件中定义布局:

layouts/base.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>{{ .title }}</title>
</head>
<body>
    {{ template "partials/header" . }}
    
    <main>
        {{ block "content" . }}{{ end }}
    </main>
    
    {{ template "partials/footer" . }}
</body>
</html>

partials/header.html

{{ define "partials/header" }}
<header>
    <nav>
        <a href="/">首页</a>
        <a href="/about">关于</a>
    </nav>
</header>
{{ end }}

partials/footer.html

{{ define "partials/footer" }}
<footer>
    <p>&copy; 2024 我的网站</p>
</footer>
{{ end }}

pages/index.html

{{ define "content" }}
<h1>{{ .message }}</h1>
<p>这是首页内容</p>
{{ end }}

渲染时指定基础模板:

r.GET("/", func(c *gin.Context) {
    c.HTML(http.StatusOK, "layouts/base.html", gin.H{
        "title": "首页",
        "message": "欢迎访问",
    })
})

模板中使用变量

在处理函数中传递数据:

r.GET("/user/:id", func(c *gin.Context) {
    user := struct {
        ID    int
        Name  string
        Email string
    }{
        ID:    1,
        Name:  "张三",
        Email: "zhangsan@example.com",
    }
    
    c.HTML(http.StatusOK, "user.html", gin.H{
        "title": "用户详情",
        "user":  user,
    })
})

模板中访问:

<h1>用户:{{ .user.Name }}</h1>
<p>邮箱:{{ .user.Email }}</p>
<p>ID:{{ .user.ID }}</p>

条件判断和循环

模板支持条件判断:

{{ if .user.IsAdmin }}
    <span class="badge">管理员</span>
{{ else }}
    <span class="badge">普通用户</span>
{{ end }}

循环遍历:

<ul>
{{ range .users }}
    <li>{{ .Name }} - {{ .Email }}</li>
{{ end }}
</ul>

自定义模板函数

Gin 允许注册自定义函数:

r.SetFuncMap(template.FuncMap{
    "safe": func(str string) template.HTML {
        return template.HTML(str)
    },
    "upper": func(str string) string {
        return strings.ToUpper(str)
    },
    "formatDate": func(t time.Time) string {
        return t.Format("2006-01-02 15:04:05")
    },
})

r.LoadHTMLGlob("templates/*")

在模板中使用:

<p>{{ .content | safe }}</p>
<p>{{ .name | upper }}</p>
<p>{{ .createdAt | formatDate }}</p>

注意:SetFuncMap 要在 LoadHTMLGlob 之前调用。

多模板文件

如果需要加载不同目录的模板:

r.LoadHTMLFiles(
    "templates/index.html",
    "templates/about.html",
    "templates/contact.html",
)

热重载

开发模式下,每次修改模板都希望立即生效:

func main() {
    r := gin.Default()
    
    if gin.Mode() == gin.DebugMode {
        r.LoadHTMLGlob("templates/**/*")
    }
    
    r.GET("/reload", func(c *gin.Context) {
        r.LoadHTMLGlob("templates/**/*")
        c.String(http.StatusOK, "模板已重新加载")
    })
    
    r.Run(":8080")
}

生产环境建议在启动时一次性加载,不要热重载。

小结

Gin 的 HTML 模板功能基于 Go 标准库,支持模板继承、自定义函数等特性。对于传统的服务端渲染应用,这些功能完全够用。如果项目复杂度较高,也可以考虑集成第三方模板引擎如 Pongo2。