
在 Go 模板中,. 代表传入的顶层数据;若直接传入切片(如 []Item),则 . 就是该切片本身,无法通过 .resItems 访问——必须改用结构体或 map 封装多个切片才能分别引用。
在 go 模板中,`.` 代表传入的顶层数据;若直接传入切片(如 `[]item`),则 `.` 就是该切片本身,无法通过 `.resitems` 访问——必须改用结构体或 map 封装多个切片才能分别引用。
当你调用 tmpl.ExecuteTemplate(w, "HomeTemplate", resItems) 时,模板接收到的数据就是 []Item 类型的切片本身,此时 . 即为该切片。因此 {{ range . }} 能正常工作——它逐个迭代切片中的每个 Item;而 {{ range .resItems }} 会失败,因为 Go 模板试图在切片类型上访问名为 resItems 的字段或方法,但切片没有该成员,导致渲染时报错:can't evaluate field resItems in type []main.Item。
✅ 正确做法:将多个切片统一包装进一个容器中传递给模板。推荐两种方式:
方式一:使用 map[string]interface{}(灵活、无需定义新类型)
// main.go
data := map[string]interface{}{
"Products": resItems,
"Categories": []string{"Electronics", "Books", "Clothing"},
"Tags": []string{"new", "sale", "featured"},
}
tmpl.ExecuteTemplate(w, "HomeTemplate", data)
对应模板(tmpl/HomeTemplate.html):
<h3>Products</h3>
{{ range .Products }}
<div>{{ .Name }} (ID: {{ .Id }}, Type: {{ .Type }})</div>
{{ end }}
<h3>Categories</h3>
{{ range .Categories }}
<span>{{ . }}</span>
{{ end }}
<h3>Tags</h3>
{{ range .Tags }}
<badge>{{ . }}</badge>
{{ end }}
方式二:使用自定义结构体(类型安全、可读性强、推荐用于生产环境)
type HomeData struct {
Products []Item
Categories []string
Tags []string
PageTitle string
}
// 构造并传递
data := HomeData{
Products: resItems,
Categories: getCategories(), // 假设你有此函数
Tags: []string{"hot", "trending"},
PageTitle: "Dashboard",
}
tmpl.ExecuteTemplate(w, "HomeTemplate", data)
模板中可直接按字段名访问:
<title>{{ .PageTitle }}</title>
{{ range .Products }}...{{ end }}
{{ range .Categories }}...{{ end }}
⚠️ 注意事项:
- 避免在模板中做复杂逻辑(如嵌套判断多层
if或手动索引切片),应尽量在 Go 代码中预处理好数据; - 若切片可能为空,请在模板中用
{{ if .Products }}{{ range .Products }}...{{ end }}{{ else }}<p>No items</p>{{ end }}做空值防护; -
map[string]interface{}虽灵活,但缺乏编译期检查;长期维护建议优先采用结构体 + 字段标签(如json:"products")提升可读性与 IDE 支持; - 切记:
defer db.Close()应放在selDB.Next()循环之前(当前代码中写在tmpl.ExecuteTemplate后,且未确保执行),正确位置应在db := database.DbConn()之后立即 defer,否则存在连接泄漏风险。
掌握这种“单入口、多字段”的数据封装模式,是构建可扩展 Go Web 模板系统的关键基础。










