
本文介绍一种不依赖变量重赋值(Go 模板中不可变)的优雅方案:通过 range 获取索引 $index,结合自定义模板函数 each 利用模运算动态生成 <tbody>、<tr> 和 <td> 结构,从而准确绑定数据数组并渲染 9×9 表格。
本文介绍一种不依赖变量重赋值(go 模板中不可变)的优雅方案:通过 `range` 获取索引 `$index`,结合自定义模板函数 `each` 利用模运算动态生成 `
`、`在 Go 的 html/template 中,模板变量是只读的——{{$count := inc $count}} 并不会真正“递增”变量,而是每次创建一个新局部变量,因此无法实现跨循环迭代的计数器。强行模拟状态会破坏模板的声明式本质,也极易出错。
正确的思路是:放弃手动维护计数器,转而利用 range 提供的内置索引 $index 和数据本身进行结构化渲染。
✅ 推荐方案:基于索引与模运算的语义化渲染
首先,在 Go 服务端注册一个轻量级模板函数 each,用于判断当前索引是否应触发某一层级标签的开始:
func HomeHandler(w http.ResponseWriter, req *http.Request) {
t := template.New("home.html").Funcs(template.FuncMap{
"each": func(interval, n int) bool {
return n%interval == 0
},
})
// 假设 MyArray 是长度为 81 的字符串切片(9×9=81)
data := struct {
MyArray []string `json:"my_array"`
}{
MyArray: []string{"1", "2", "3", /* ... 共 81 个值 */},
}
t, err := t.ParseFiles("templates/home.html")
if err != nil {
log.Printf("template parse error: %v", err)
http.Error(w, "Template error", http.StatusInternalServerError)
return
}
t.ExecuteTemplate(w, "home.html", data)
}然后在 home.html 模板中,使用 $index 配合 each 函数自动插入结构标签:
<table>
{{ range $index, $value := .MyArray }}
{{ if eq (mod $index 27) 0 }}<tbody>{{ end }}
{{ if eq (mod $index 9) 0 }}<tr>{{ end }}
<td><input value="{{ $value }}" type="text" name="value[]" maxlength="1" size="1"></td>
{{ if eq (mod (add $index 1) 9) 0 }}</tr>{{ end }}
{{ if eq (mod (add $index 1) 27) 0 }}</tbody>{{ end }}
{{ end }}
</table>? 注:标准 html/template 不内置 mod 或 add,因此更稳妥的做法是将 each 函数设计为返回布尔值(如答案所示),或预先注册 add/mod 函数。但为最小依赖,我们沿用原答案中的 each 设计:
{{ range $index, $value := .MyArray }}
{{ if each 27 $index }}<tbody>{{ end }}
{{ if each 9 $index }}<tr>{{ end }}
<td><input value="{{ $value }}" type="text" name="value[]" maxlength="1" size="1"></td>
{{ if each 9 (add $index 1) }}</tr>{{ end }}
{{ if each 27 (add $index 1) }}</tbody>{{ end }}
{{ end }}⚠️ 注意:若未注册 add 函数,需在 FuncMap 中补充:
"add": func(a, b int) int { return a + b },
? 为什么这样更可靠?
- 无状态:完全避免变量赋值陷阱,符合 Go 模板设计哲学;
- 可读性强:each 9 $index 直观表达“每 9 项开始一行”,语义清晰;
- 可扩展:轻松适配任意维度(如 3×3 区块、16×16 数独等),只需调整模数;
- 数据驱动:HTML 结构由数据长度和索引逻辑决定,而非硬编码循环嵌套,不易错位。
✅ 最终效果
对于长度为 81 的 .MyArray,上述模板将精确生成:
- 每 27 项(即 3 行 × 9 列)包裹一个 <tbody>;
- 每 9 项开启一个 <tr>,并在第 9、18、…、81 项后闭合;
- 每个 <td> 绑定对应位置的 .MyArray[$index] 值。
无需 loop 辅助函数,无需脆弱的手动计数器 —— 简洁、健壮、一次写对。

















