第26讲 Gin的公共模板嵌套
本讲基于视频实际讲解内容整理
一、本讲概述
上一讲我们学会了渲染单个 HTML 页面。但一个网站有很多页面,每个页面都要写一遍 <head>、导航栏、页脚,会产生大量重复代码。本讲讲解如何用 Go 模板的嵌套/继承机制,把公共部分抽成"公共模板",让每个页面只写自己独有的内容。
二、为什么需要公共模板
2.1 重复代码的痛点
没有公共模板时,每个页面都要重复写:
HTML
<!-- index.html -->
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>BBS</title></head>
<body>
<nav>导航栏...</nav>
<div>首页内容</div>
<footer>页脚...</footer>
</body>
</html>
<!-- login.html -->
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>BBS</title></head>
<body>
<nav>导航栏...</nav> <!-- 又写了一遍 -->
<div>登录表单</div>
<footer>页脚...</footer> <!-- 又写了一遍 -->
</body>
</html>
老师指出:导航栏、页脚在每个页面都一样,复制粘贴既臃肿又难维护。改一处要改所有页面,很容易漏。
2.2 目标:抽公共、留个性
把 <head>、<nav>、<footer> 抽到公共模板,每个页面只写中间不同的内容。
三、Go 模板的 define 与 template
3.1 define:定义一个命名模板
用 {{define "名字"}}...{{end}} 可以把一段模板定义为命名块:
HTML
<!-- templates/base.html -->
{{define "base"}}
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>BBS</title></head>
<body>
<nav>导航栏...</nav>
{{template "content" .}} <!-- 在这里嵌入子模板 -->
<footer>页脚...</footer>
</body>
</html>
{{end}}
3.2 template:引用(嵌套)另一个模板
{{template "content" .}} 表示"在这里插入名为 content 的模板",. 表示把当前数据传进去。
HTML
<!-- templates/index.html -->
{{define "content"}}
<h1>首页内容</h1>
<p>欢迎访问 BBS</p>
{{end}}
{{template "base" .}} <!-- 引入公共布局,并把数据透传 -->
老师说明:
{{template "base" .}}让index.html整体套用base布局,而base里的{{template "content" .}}又会找当前文件(或同批加载的模板)里定义的content。公共部分和个性内容就拼起来了。
四、用 block 定义可覆盖的默认块
4.1 block 的作用
{{block "名字" .}}默认内容{{end}} 和 template 类似,但如果子模板没有提供该块,就使用默认内容:
HTML
<!-- templates/base.html -->
{{define "base"}}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>{{block "title" .}}BBS默认标题{{end}}</title>
</head>
<body>
<nav>导航栏...</nav>
{{block "content" .}}<p>默认内容</p>{{end}}
<footer>页脚...</footer>
</body>
</html>
{{end}}
4.2 子页面覆盖块
HTML
<!-- templates/login.html -->
{{define "title"}}用户登录{{end}}
{{define "content"}}
<h1>登录</h1>
<form method="post" action="/login">
用户名:<input name="username"><br>
密码:<input type="password" name="password"><br>
<button type="submit">登录</button>
</form>
{{end}}
{{template "base" .}}
老师强调:
block适合做"可选覆盖"。比如title大部分页面用默认的,登录页想改标题就自己define "title"覆盖掉。这样公共模板更灵活。
五、完整示例
5.1 目录结构
TEXT
templates/
├── base.html ← 公共布局(head/nav/footer)
├── index.html ← 首页(只写 content)
└── login.html ← 登录页(只写 content + title)
5.2 handler 中照常渲染
Go
func Index(c *gin.Context) {
c.HTML(200, "index.html", gin.H{
"username": "张三",
})
}
func LoginPage(c *gin.Context) {
c.HTML(200, "login.html", nil)
}
HTML
<!-- templates/index.html -->
{{define "content"}}
<h1>欢迎 {{.username}}</h1>
<p>这是 BBS 首页</p>
{{end}}
{{template "base" .}}
注意:因为是
LoadHTMLGlob("templates/*")一次性加载所有模板,base、content、title这些命名块彼此都能互相找到,嵌套才能生效。
六、本讲小结
| 知识点 | 要点 |
|---|---|
| 痛点 | 每个页面重复 head/nav/footer |
define |
{{define "名"}}...{{end}} 定义命名模板 |
template |
{{template "名" .}} 嵌入另一个模板 |
block |
可覆盖的默认块,子页不定义就用默认 |
| 公共布局 | base.html 放公共结构,页面只写 content |
| 渲染 | handler 仍用 c.HTML(200, "页面.html", 数据) |
七、课后练习
- 新建
templates/base.html,把<head>、导航栏、页脚放进{{define "base"}}中 - 在
base中用{{block "content" .}}预留内容区,用{{block "title" .}}预留标题 - 新建
index.html和login.html,各自只define "content",最后{{template "base" .}} - 让
login.html额外define "title"覆盖默认标题 - 在
main.go中用LoadHTMLGlob("templates/*")加载,并渲染两个页面验证嵌套效果 - 思考:如果再增加一个"注册页",需要复制多少代码?公共模板带来了什么好处?