空切片传入{{range .}}会跳过,但结构体其他零值字段(如.Title、.ID)仍可能渲染空白;应优先用{{with .Items}}判断非空并隔离零值污染,配合后端兜底默认值。

空切片传入 {{range .}} 会直接跳过,但结构体字段零值仍可能渲染空白
很多人以为只要切片是空的,{{range .}} 就不会输出任何内容——这没错。但问题常出在:你传给模板的不是纯切片,而是包含切片字段的结构体,且该结构体其他字段(如 .Title、.ID)在切片为空时仍为零值(""、0、nil)。一旦模板里写了 <h3>{{.Title}}</h3> 或 <tr><td>{{.ID}}</td></tr>,就会渲染出空标签。
典型现象:表格第二行全空、标题下方出现 <h3></h3>、按钮文字消失但 DOM 节点还在。
- 检查控制器传入的数据是否真的“干净”:用
fmt.Printf("%+v", data)打印结构体,确认所有非切片字段都有合理默认值(比如Title: "暂无数据"而非"") - 避免在模板中直接访问可能为零值的字段,改用
{{if .Title}}{{.Title}}{{else}}暂无标题{{end}} - 如果切片是核心展示内容,建议把整个结构体设计成“以切片为根”,例如传
c.HTML(200, "list.tmpl", gin.H{"items": items}),而不是嵌套在PageData{Items: items}里再暴露其他易零值字段
用 {{with}} 包裹切片,显式控制空状态的 UI
{{with .Items}} 是比 {{range .Items}} 更安全的起点:它先判断切片是否非空(长度 > 0),只在非空时才进入块内,并将 . 绑定为该切片本身。这样既能避免空遍历,又能自然隔离零值污染。
示例:
{{with .Items}}
<table>
{{range .}}
<tr><td>{{.Name}}</td><td>{{.ID}}</td></tr>
{{end}}
</table>
{{else}}
<div class="empty">暂无条目</div>
{{end}}
-
{{with}}内部的.指向.Items切片,所以{{range .}}等价于{{range .Items}},语义清晰 - 不要写
{{with .Items}}{{range .Items}}...{{end}}{{end}}——重复引用既冗余又易错 - 若需在
{{with}}块内访问外层字段(如.PageTitle),必须用{{$}}显式回溯到根作用域:{{with .Items}}...{{$}}.PageTitle{{end}}
后端提前校验并统一兜底,别把空责任全推给模板
模板不是纠错层。空切片本身不可怕,可怕的是它来自一个本不该为空的查询结果(比如用户有权限看列表,但 SQL 返回了空 slice 而不是报错)。这时模板加再多 {{if}} 都治标不治本。
- 数据库查询后,检查
len(rows)是否为 0 且业务逻辑要求必须有数据 → 主动返回 404 或封装错误响应,而不是硬塞空切片 - 对前端必显字段(如列表页的
title、description),在构造模板数据前就做默认赋值:data := gin.H{"items": items, "title": titleOrDefault(title, "全部项目")} - 如果切片来自多个异构查询合并(如 PostgreSQL 多表 union 后强转同一结构体),务必确保各查询返回字段类型一致,否则零值填充会导致
{{.Name}}渲染为空字符串而非跳过
注意 LoadHTMLGlob 加载顺序与模板继承中的 . 作用域陷阱
当使用 {{template "sub" .}} 传递数据给子模板时,如果主模板用了 {{range}} 或 {{with}},子模板接收到的 . 可能已被重绑定为当前项或切片,而非原始结构体——这会让空判断逻辑失效。
- 子模板中不要假设
.一定有.Items字段;要么显式传参:{{template "sub" dict "items" .Items "title" $.Title}},要么在子模板开头用{{if .items}}而非{{if .Items}} -
LoadHTMLGlob("templates/**/*")会按文件名 ASCII 序加载,若子模板(如_base.tmpl)被后加载,而主模板(index.tmpl)已引用它,可能导致未定义模板错误——空切片问题虽不直接相关,但这类加载不确定性会让调试更难 - 避免在 base 模板里写
{{range .}}并期望所有子模板都传切片;base 应保持通用,具体遍历逻辑下沉到子模板中


















