Go 模板语法详解
🥰个人主页:会编程的土豆(欢迎来访)
💎作者简介:后端学习者
❄️个人专栏:数据结构与算法,数据库,leetcode
✨那些你一个人走过的夜路,终将化作照亮未来的光
目标:吃透
{{ }}里的动作(action):输出、变量、条件、循环、with、管道与内置函数。这是后面嵌套/继承/Gin 的语法基础。
1. 模板语法总览
Go 模板里,普通文本原样输出;逻辑都写在action中:
{{ ... }}常见类别:
| 类别 | 例子 | 作用 |
|---|---|---|
| 输出 | {{.Name}} | 打印值 |
| 注释 | {{/* ... */}} | 不输出 |
| 变量 | {{$x := .}} | 绑定到$x |
| 条件 | {{if}}...{{end}} | 分支 |
| 循环 | {{range}}...{{end}} | 遍历 |
| 改上下文 | {{with}}...{{end}} | 临时切换. |
| 管道 | {{. | printf "%s"}} | 函数链式处理 |
| 模板调用 | {{template "x" .}} | 嵌套(下节细讲) |
2. 点(dot).:当前上下文
.是模板里最重要的概念
Execute(w, data)时,根上下文.=data- 进入
range/with后,.可能变成「当前元素」 - 离开
range/with后,.恢复
例子
type Product struct { Name string Price int } type Page struct { ShopName string Items []Product }店铺:{{.ShopName}} {{range .Items}} <!-- 这里的 . 是 Product --> 商品:{{.Name}},价格:{{.Price}} {{end}} <!-- 这里 . 又回到 Page --> 再次显示店铺:{{.ShopName}}如果在range里还想访问外层的ShopName,不能写{{.ShopName}}(因为.已是 Product),要用变量把外层存起来
3. 输出与注释
3.1 输出字段 / 键 / 方法
{{.Name}} <!-- struct 导出字段 --> {{.User.Email}} <!-- 链式字段 --> {{index .Map "k"}} <!-- map 取值的稳妥写法 --> {{.FullName}} <!-- 无参方法,方法名需导出 -->方法示例:
type User struct { First string Last string } func (u User) FullName() string { return u.First + " " + u.Last }{{.FullName}}注意:方法若返回(value, error),模板执行时若 error != nil 会失败
3.2 注释
{{/* 这是注释,不会出现在最终 HTML 里 */}}注释不能嵌套,内容里也不要随便写*/}}这类结束符
3.3 什么时候会「空输出」?
访问不到的字段、nil 指针的字段等,在默认设置下可能输出为空,或在missingkey等选项下报错。调试时建议先把数据结构打印出来,确认字段名大小写一致
4. 变量:$name
用$定义模板变量:
{{$title := .Title}} <h1>{{$title}}</h1>为什么需要变量?
最经典场景:range 里访问外层数据
{{$shop := .ShopName}} {{range .Items}} <li>{{$shop}} - {{.Name}}</li> {{end}}也可以在range时直接接收索引和元素:
{{range $i, $item := .Items}} <li>#{{$i}} {{$item.Name}}</li> {{end}}赋值与重新绑定
{{$x := 1}} {{$x = 2}} <!-- 重新赋值(Go 1.11+ 模板支持) -->入门更常见的是:=定义一次后只读使用
作用域
变量一般在定义它的{{if}}/{{range}}/{{with}}/...{{end}}块内有效;在块外可能不可见(以你使用的 Go 版本与定义位置为准)。实践建议:在需要的最小范围内定义。
5. 条件:if/else/else if
{{if .LoggedIn}} <p>欢迎,{{.Name}}</p> {{else}} <p>请先登录</p> {{end}}什么算「真」?
模板把下列值视为false(不进入if主体):
false- 数字
0 - 空字符串
"" nil- 空的 array / slice / map / chan
其它一般视为 true。
多分支
{{if eq .Role "admin"}} 管理员面板 {{else if eq .Role "editor"}} 编辑面板 {{else}} 普通用户 {{end}}比较函数(很常用)
这些是内置函数,不是操作符写成==:
| 函数 | 含义 |
|---|---|
eq | == |
ne | != |
lt | < |
le | <= |
gt | > |
ge | >= |
{{if gt .Age 18}} 成年 {{end}} {{if eq .Status "ok" "ready"}} <!-- eq 支持多参数:与任一相等即为真 --> 可用 {{end}}逻辑与或非:
{{if and .A .B}} ... {{end}} {{if or .A .B}} ... {{end}} {{if not .Hidden}} ... {{end}}6.with:临时切换上下文
with有两个作用:
- 判断值是否为真
- 若为真,在块内把
.换成这个值
{{with .Profile}} <p>城市:{{.City}}</p> <p>签名:{{.Bio}}</p> {{else}} <p>用户未填写资料</p> {{end}}等价心智模型:
如果 Profile 有值: 进入块,且 . = Profile 否则: 走 elsewith+ 变量
{{with $p := .Profile}} {{$p.City}} {{end}}ifvswith
if | with | |
|---|---|---|
是否切换. | 否 | 是 |
| 适合 | 纯条件判断 | 深入嵌套结构体字段 |
很多人写{{if .Profile}}{{.Profile.City}}{{end}},用with更干净。
7.range:遍历
可遍历:slice、array、map、channel。
7.1 基础遍历
<ul> {{range .Items}} <li>{{.Name}} - ¥{{.Price}}</li> {{else}} <li>暂无商品</li> {{end}} </ul>else分支在集合为空时执行,非常适合空状态 UI。
7.2 索引与元素
{{range $index, $product := .Items}} <tr> <td>{{$index}}</td> <td>{{$product.Name}}</td> </tr> {{end}}遍历 map:
{{range $key, $value := .Labels}} <span>{{$key}}={{$value}}</span> {{end}}注意:map 的遍历顺序不稳定(随机),若要稳定展示,应在 Go 里先排好序再传给模板,或传[]struct{Key, Value}。
7.3 range 里修改了.
{{range .Items}} <!-- . 是元素 --> {{.Name}} {{end}}若只写{{range .Items}}不接变量,元素就绑定到.。
8. 管道(pipeline)
管道用|,把左边结果作为右边函数的最后一个参数。
{{.Name | printf "用户:%s"}}等价于:printf("用户:%s", .Name)。
多级管道
{{.Name | printf "%s" | html}}(在html/template中,上下文感知转义通常已自动发生;这里主要演示管道形态。)
常见内置函数
| 函数 | 作用 | 例子 |
|---|---|---|
printf | 格式化 | {{printf "%.2f" .Price}} |
len | 长度 | {{len .Items}} |
index | 下标/键访问 | {{index .Items 0}} |
slice | 切片(新版本支持) | {{slice .Items 0 3}} |
and/or/not | 逻辑 | 见上 |
urlquery | 查询参数转义 | {{.Q | urlquery}} |
js/html等 | 按上下文转义 | 较少手写 |
index详解
{{index .Scores 0}} <!-- slice[0] --> {{index .UserMap "u100"}} <!-- map["u100"] --> {{index .Matrix 1 2}} <!-- 多维:先 [1] 再 [2] -->当字段名是动态字符串、或键不是合法标识符时,index很有用。
printf详解
{{printf "%s-%d" .Name .Age}} {{.Price | printf "¥%d"}}9. 综合示例:商品列表页
main.go数据:
package main import ( "html/template" "os" ) type Product struct { Name string Price int Tags []string } type Page struct { Title string ShopName string VIP bool Items []Product } func main() { tmpl := template.Must(template.New("list").Parse(listHTML)) data := Page{ Title: "今日特价", ShopName: "七米小店", VIP: true, Items: []Product{ {Name: "Go 书", Price: 88, Tags: []string{"编程", "热销"}}, {Name: "键盘", Price: 299, Tags: []string{"外设"}}, }, } tmpl.Execute(os.Stdout, data) } const listHTML = ` <!DOCTYPE html> <html> <head><meta charset="utf-8"><title>{{.Title}}</title></head> <body> <h1>{{.Title}}</h1> <p>店铺:{{.ShopName}}</p> {{if .VIP}} <p>VIP 专属折扣已开启</p> {{else}} <p>开通 VIP 更优惠</p> {{end}} <p>共 {{len .Items}} 件商品</p> {{$shop := .ShopName}} <ul> {{range $i, $p := .Items}} <li> #{{$i}} [{{$shop}}] <strong>{{$p.Name}}</strong> {{printf "¥%d" $p.Price}} {{with $p.Tags}} 标签: {{range .}} <span>{{.}}</span> {{end}} {{end}} </li> {{else}} <li>暂无商品</li> {{end}} </ul> {{with index .Items 0}} <p>推荐:{{.Name}}</p> {{end}} </body> </html> `这个例子一口气用到了:
- 输出、
if、len $shop保留外层range带索引with处理标签printf管道式格式化index取第一件商品
建议你改数据:把Items置空、把VIP改false,观察分支变化。
10. 空值与安全输出意识
指针字段
type User struct { Name *string }若Name == nil,{{.Name}}可能出问题或为空。更稳妥是在 Go 侧先规范化数据,或模板里:
{{with .Name}}{{.}}{{else}}匿名{{end}}HTML 转义
html/template会按插入点上下文转义。用户可控内容不要随便用template.HTML关闭转义(lesson08 会讲)。
缺失键
对 map,可设置:
tmpl.Option("missingkey=error")让访问不存在的 key 直接报错,便于调试
