go template 中 range 遍历 nil 切片会 panic,需用 {{if .items}}{{range .items}}...{{end}}{{else}}暂无数据{{end}} 安全处理;遍历空 slice 则静默跳过;变量命名需规范(如 $i, $v),嵌套时须显式命名避免覆盖。

range 在 Go template 中怎么写才不会 panic
直接用 range 遍历空 slice 或 nil 切片会 panic,比如 {{range .Items}}...{{end}} 中 .Items 是 nil,模板执行时抛出 template: xxx: nil pointer evaluating interface {}.Items。这不是语法错,是运行时数据缺失导致的。
安全写法是加一层判断:
{{if .Items}}
{{range .Items}}
<div>{{.Name}}</div>
{{end}}
{{else}}
<div>暂无数据</div>
{{end}}
-
range本身不处理 nil,必须由外层if或with拦截 - 如果确定数据非 nil,但可能为空 slice,
range会静默跳过(不报错),此时无需额外判断 - 结构体字段未导出(小写开头)在模板中不可见,
range取到的项也无法访问其字段
range 的三种常用形式和对应变量名
Go template 的 range 行为取决于被遍历对象类型,变量名规则固定,写错就取不到值。
遍历切片或数组时,默认暴露当前元素(.):
{{range .Users}}
{{.ID}} - {{.Name}}
{{end}}
想同时拿到索引和元素,用两个点号接收:
{{range $index, $user := .Users}}
{{$index}}: {{$user.Name}}
{{end}}
遍历 map 时,必须用两个变量,且顺序固定:第一个是 key,第二个是 value:
{{range $k, $v := .Config}}
{{$k}} = {{$v}}
{{end}}
- 变量名前加
$是为了在嵌套作用域中避免覆盖外层同名变量 - 不加
$也能用(如index, user),但一旦内层有同名字段(如.Index)就会冲突 - map 的 key 类型必须是可比较的(string、int、bool 等),不能是 slice、func、map
嵌套 range 怎么避免变量名污染
多层 range 嵌套时,内层 . 会覆盖外层 .,不显式命名容易误用。例如用户带权限列表,想渲染每个用户的每条权限:
{{range $user := .Users}}
<h3>{{$user.Name}}</h3>
{{range $perm := $user.Permissions}}
<span>{{$perm.Action}}</span> <!-- 这里 . 已经是 $perm,不是 $user -->
{{end}}
{{end}}
- 外层
range必须用命名(如$user),否则内层无法反向引用 - 内层也建议命名(如
$perm),避免和结构体字段名冲突(比如权限结构体也有Name字段) - 不要依赖
$.Users回溯顶层数据——虽然可行,但可读性差,且在复杂嵌套中容易失效
template.New().Funcs() 会影响 range 吗
不会。range 是模板内置动作,不受自定义函数影响。但如果你在 range 内部调用自定义函数,要注意传参类型匹配。
比如注册了一个格式化时间的函数:
tmpl := template.New("page").Funcs(template.FuncMap{
"formatTime": func(t time.Time) string { return t.Format("2006-01-02") },
})
然后在模板中这样用:
{{range .Posts}}
{{.Title}} ({{formatTime .CreatedAt}})
{{end}}
-
range本身不感知 FuncMap,它只负责控制流 - 但
.CreatedAt必须是time.Time类型,否则formatTime调用会 panic - 如果数据来自 JSON 解析(
json.Unmarshal),时间字段默认是 string,需提前转成time.Time,模板里没法做类型转换
最易忽略的是数据准备阶段:range 很听话,但它只能遍历你给它的东西——类型对不对、是不是 nil、字段是否导出,这些都得在 Execute 之前确认好。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











