登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

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 或更高版本,或者在进入模板前先收集成切片。

Go 切片、slices.All、iter.Seq2 与模板字段之间的静态数据结构图
图1:静态结构图。数据源经 slices.All 表示为 iter.Seq2,并作为 .Items 字段交给模板;图片不是运行截图。

二、把 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 时“单变量得到元素值”的习惯不同。因此,不要依赖一个变量或隐式的点号来猜测当前值,成对数据直接写两个变量最清楚。

Go 模板 range 对 iter.Seq2 两个产出值的变量绑定与约束边界图
图2:静态边界图。两个变量分别绑定 Seq2 的第一值和第二值,单变量、空序列与顺序是需要单独确认的约束。

四、空序列、顺序与提前结束怎么处理

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

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>