Go template 里怎么遍历 iter.Seq2 数据
来源:17golang原创
时间:2026-10-07 00:45:01 416浏览 收藏
Go 1.24 及更高版本的 text/template 可以直接用 {{range}} 遍历 iter.Seq2。把迭代器作为模板数据的公开字段传进去,然后写 {{range $first, $second := .Items}},两个变量会依次接收 Seq2 每次 yield 的第一、第二个值。最容易踩坑的是只声明一个变量:对 Seq2 来说,它接收的是第一个产出值,而不是通常被理解为“元素”的第二个值。
iter.Seq2类型在 Go 1.23 引入,但text/template支持 range-over-func 是 Go 1.24 的变化。- 模板中直接 range Seq2,不需要写
call,也不必先收集成切片。 - 推荐始终写两个变量,明确第一值和第二值的含义。
- 迭代顺序由 Seq2 自己决定,需要排序时应在 Go 侧处理。
一、先确认工具链与 Seq2 的数据形态
iter.Seq2[K, V] 本质上是 func(yield func(K, V) bool)。迭代器每得到一对数据就调用一次 yield(k, v);当模板中的 range 正常结束时继续产出,遇到提前终止则会让 yield 返回 false。当前 text/template 文档已经把 iter.Seq、iter.Seq2 和整数列为 range 可接受的数据类型。
如果项目还使用 Go 1.23,Go 代码里的 for range 已能遍历迭代器,但模板执行器还不能直接 range 这类函数。升级到 Go 1.24 或更高版本,或者在进入模板前先收集成切片。

二、把 Seq2 放进模板数据
下面用 slices.All 把字符串切片变成 iter.Seq2[int, string]。数据结构直接保存迭代器,模板中的 .Items 就是这个函数值。对于 range 支持的迭代器,不需要再套预定义的 call 函数。
package main
import (
"iter"
"os"
"slices"
"text/template"
)
type ViewData struct {
// Items 每次产出索引和对应字符串。
Items iter.Seq2[int, string]
}
func main() {
items := []string{"alpha", "beta", "gamma"}
// 显式声明两个模板变量,避免单变量时只接收第一产出值。
const src = `{{range $i, $item := .Items}}{{$i}}: {{$item}}
{{else}}没有数据
{{end}}`
t := template.Must(template.New("list").Parse(src))
data := ViewData{Items: slices.All(items)}
// Execute 会消费 Items 产生的成对数据,并写入标准输出。
if err := t.Execute(os.Stdout, data); err != nil {
panic(err)
}
}
模板得到的结果是索引和值成对出现:
0: alpha
1: beta
2: gamma
三、用两个变量明确绑定关系
对于 iter.Seq2[K, V],最稳妥的模板写法是:
// 模板动作中的两个变量分别绑定 Seq2 的第一值和第二值。
const pairRange = `{{range $k, $v := .Pairs}}{{$k}}={{$v}}{{end}}`
如果只写 {{range $x := .Pairs}},$x 得到的是 Seq2 的第一产出值。官方 text/template 文档特别说明,这一点与模板遍历 map 或 slice 时“单变量得到元素值”的习惯不同。因此,不要依赖一个变量或隐式的点号来猜测当前值,成对数据直接写两个变量最清楚。

四、空序列、顺序与提前结束怎么处理
Seq2 没有调用过 yield 时,{{else}} 分支会执行,所以空状态可以直接放在 range 后面。这个判断依据是“是否产出元素”,不是函数值本身是否为 nil。
顺序则完全由迭代器决定。slices.All 按切片索引顺序产出,因此示例稳定;如果把 maps.All 的结果直接交给模板,顺序仍来自 map 迭代,不会因为进入 template 就自动排序。页面、配置文件或测试快照需要稳定顺序时,应在 Go 侧先排序,再构造 Seq2。
模板中的 {{break}} 和 {{continue}} 仍可用于 range。前者会结束最内层循环,迭代器收到停止信号后也应停止继续调用 yield;自定义 Seq2 必须遵守 yield 返回 false 后立即返回的约定。
五、什么时候应该先收集成切片
| 约束 | 直接传 Seq2 | 先收集成切片 |
|---|---|---|
| 数据量 | 适合惰性产生、无需全部常驻内存 | 适合规模可控、需要重复读取 |
| 顺序 | 沿用迭代器提供的顺序 | 更方便先排序再渲染 |
| 重复执行模板 | 取决于迭代器是否可重放 | 切片通常可安全重复遍历 |
| 中途错误 | Seq2 没有第三个 error 产出位 | 可在渲染前完成读取与错误处理 |
如果数据来自可能失败的 I/O、需要分页总数、要多次执行同一模板,或者输出必须排序,先在 Go 侧准备完整视图模型往往更简单。Seq2 最适合已经有迭代器 API、数据可惰性产生、模板只需单次顺序消费的场景。
落地检查清单
- 运行环境是 Go 1.24 或更高版本。
- 模板数据字段导出,实际类型为
iter.Seq2[K, V]或同形函数。 - range 中写两个变量,清楚标注第一值和第二值。
- 为零产出序列提供
{{else}}。 - 需要稳定顺序时,在构造迭代器前完成排序。
- 自定义迭代器在 yield 返回 false 后立即停止。
相关问题
为什么写一个变量时拿到的是索引?
因为 Seq2 的单变量模板 range 绑定第一产出值;slices.All 的第一值正好是索引。需要元素时应写 {{range $i, $item := .Items}}。
可以把 Seq2 放进 FuncMap 吗?
可以。函数名在模板管道中会被调用,其结果可以交给 range;但如果迭代器已经是数据字段,直接使用 .Items 更容易看清依赖。
html/template 也能这样遍历吗?
html/template 与 text/template 共享模板语义,并增加上下文转义。生成 HTML 时应优先使用 html/template,遍历方式不变。
资料来源
Go 1.24 Release Notes:https://go.dev/doc/go1.24
text/template 官方文档:https://pkg.go.dev/text/template
iter 官方文档:https://pkg.go.dev/iter
-
369 收藏
-
344 收藏
-
464 收藏
-
327 收藏
-
206 收藏
-
148 收藏
-
151 收藏
-
271 收藏
-
290 收藏
-
415 收藏
-
466 收藏
-
326 收藏
-
377 收藏
-
332 收藏
-
469 收藏
-
343 收藏
-
427 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习