Go语言模板语法举例详细说明并不只看表面做法,关键还要理解相关条件、限制和后续影响。
目标:吃透 {{ }} 里的动作(action):输出、变量、条件、循环、with、管道与内置函数。这是后面嵌套/继承/Gin 的语法基础。

Go 模板里,普通文本原样输出;逻辑都写在 action 中:
{{ ... }}常见类别:
| 类别 | 例子 | 作用 |
|---|---|---|
| 输出 | {{.Name}} | 打印值 |
| 注释 | {{/* ... */}} | 不输出 |
| 变量 | {{$x := .}} | 绑定到 $x |
| 条件 | {{if}}...{{end}} | 分支 |
| 循环 | {{range}}...{{end}} | 遍历 |
| 改上下文 | {{with}}...{{end}} | 临时切换 . |
| 管道 | {{. | printf "%s"}} | 函数链式处理 |
| 模板调用 | {{template "x" .}} | 嵌套(下节细讲) |
. 是模板里最重要的概念
Execute(w, data) 时,根上下文 . = datarange / with 后,. 可能变成「当前元素」range / with 后,. 恢复type Product struct {Name stringPrice int}type Page struct {ShopName stringItems []Product}店铺:{{.ShopName}}{{range .Items}} <!-- 这里的 . 是 Product --> 商品:{{.Name}},价格:{{.Price}}{{end}}<!-- 这里 . 又回到 Page -->再次显示店铺:{{.ShopName}}如果在 range 里还想访问外层的 ShopName,不能写 {{.ShopName}}(因为 . 已是 Product),要用变量把外层存起来
{{.Name}} <!-- struct 导出字段 -->{{.User.Email}} <!-- 链式字段 -->{{index .Map "k"}} <!-- map 取值的稳妥写法 -->{{.FullName}} <!-- 无参方法,方法名需导出 -->方法示例:
type User struct {First stringLast string}func (u User) FullName() string {return u.First + " " + u.Last}{{.FullName}}注意:方法若返回 (value, error),模板执行时若 error != nil 会失败
{{/* 这是注释,不会出现在最终 HTML 里 */}}注释不能嵌套,内容里也不要随便写 */}} 这类结束符
访问不到的字段、nil 指针的字段等,在默认设置下可能输出为空,或在 missingkey 等选项下报错。调试时建议先把数据结构打印出来,确认字段名大小写一致
用 $ 定义模板变量:
{{$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 版本与定义位置为准)。实践建议:在需要的最小范围内定义。
{{if .LoggedIn}} <p>欢迎,{{.Name}}</p>{{else}} <p>请先登录</p>{{end}}模板把下列值视为 false(不进入 if 主体):
false0""nil其它一般视为 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}}with 有两个作用:
. 换成这个值{{with .Profile}} <p>城市:{{.City}}</p> <p>签名:{{.Bio}}</p>{{else}} <p>用户未填写资料</p>{{end}}等价心智模型:
如果 Profile 有值: 进入块,且 . = Profile否则: 走 else
{{with $p := .Profile}} {{$p.City}}{{end}}| if | with | |
|---|---|---|
是否切换 . | 否 | 是 |
| 适合 | 纯条件判断 | 深入嵌套结构体字段 |
很多人写 {{if .Profile}}{{.Profile.City}}{{end}},用 with 更干净。
可遍历:slice、array、map、channel。
<ul>{{range .Items}} <li>{{.Name}} - ¥{{.Price}}</li>{{else}} <li>暂无商品</li>{{end}}</ul>else 分支在集合为空时执行,非常适合空状态 UI。
{{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}。
{{range .Items}} <!-- . 是元素 --> {{.Name}}{{end}}若只写 {{range .Items}} 不接变量,元素就绑定到 .。
管道用 |,把左边结果作为右边函数的最后一个参数。
{{.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 .Scores 0}} <!-- slice[0] -->{{index .UserMap "u100"}} <!-- map["u100"] -->{{index .Matrix 1 2}} <!-- 多维:先 [1] 再 [2] -->当字段名是动态字符串、或键不是合法标识符时,index 很有用。
{{printf "%s-%d" .Name .Age}}{{.Price | printf "¥%d"}}main.go 数据:
package mainimport ("html/template""os")type Product struct {Name stringPrice intTags []string}type Page struct {Title stringShopName stringVIP boolItems []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,观察分支变化。
type User struct {Name *string}若 Name == nil,{{.Name}} 可能出问题或为空。更稳妥是在 Go 侧先规范化数据,或模板里:
{{with .Name}}{{.}}{{else}}匿名{{end}}html/template 会按插入点上下文转义。用户可控内容不要随便用 template.HTML 关闭转义(lesson08 会讲)。
对 map,可设置:
tmpl.Option("missingkey=error")让访问不存在的 key 直接报错,便于调试