Hugo 模板函数 collections.Group 完全指南:按 Key 分组页面集合与分页实战
2026/9/18 23:38:45 网站建设 项目流程

Hugo 模板函数 collections.Group 完全指南:按 Key 分组页面集合与分页实战

【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugo

本指南深入讲解 Hugo 模板函数collections.Group(别名group):它如何将任意页面集合(slice)按自定义 Key 打包成page.PageGroup类型的分组对象,以及如何与slicefirstlast.Paginate等组合实现"按标签/新旧/任意维度分组的页面列表 + 分页"的完整实战方案。读完你将掌握group的调用签名、PageGroup结构、与内置分组方法的异同,以及可复制的分组分页模板代码。

函数签名与定位

collections.Group是 Hugo 模板层 collections 命名空间下的一个函数,在模板中通过别名group调用。其文档定义的签名如下:

collections.Group KEY PAGES
  • KEY:任意类型的分组键,在模板中通常传一个字符串(如"New""Old"),最终会成为分组对象PageGroup.Key字段的值;
  • PAGES:要分组的页面集合(slice),例如.Site.RegularPagesfirst 10 .Site.RegularPages等管道链的输出;
  • 返回值page.PageGroup,即一个"键 + 页面集合"的结构体。

对应源码位于 tpl/collections/collections.go,注释明确说明"Group groups a set of items by the given key",且当前只支持 Pages:

// Group groups a set of items by the given key. // This is currently only supported for Pages. func (ns *Namespace) Group(key any, items any) (any, error) { if key == nil { return nil, errors.New("nil is not a valid key to group by") } if g, ok := items.(collections.Grouper); ok { return g.Group(key, items) } in := newSliceElement(items) if g, ok := in.(collections.Grouper); ok { return g.Group(key, items) } return nil, fmt.Errorf("grouping not supported for type %T %T", items, in) }

从源码可以看出三点实现细节:

  1. nil不能作为 Keykey == nil会直接返回错误"nil is not a valid key to group by"
  2. 底层依赖Grouper接口collections.Group本身不实现具体分组逻辑,而是将工作委托给实现了 common/collections/collections.go 中Grouper接口的类型(该接口定义即Group(key any, items any) (any, error))。页面集合类型实现了该接口,因此可以完成分组;
  3. 非页面类型不支持:如果传入[]stringstring等普通类型,会返回"grouping not supported for type ..."错误——分组目前是 Pages 专属能力。

单元测试佐证

tpl/collections/collections_test.go 中的TestGroup用一组表格驱动用例验证了上述行为:

{"a", []*tstGrouper{{}, {}}, "a(2)"}, {"b", tstGroupers{&tstGrouper{}, &tstGrouper{}}, "b(2)"}, {"a", []tstGrouper{{}, {}}, "a(2)"}, {"a", []*tstGrouper{}, "a(0)"}, {"a", []string{"a", "b"}, false}, // 期望报错 {"a", "asdf", false}, // 期望报错 {"a", nil, false}, // 期望报错 {nil, []*tstGrouper{{}, {}}, false}, // 期望报错

可以看到:只要类型实现了Grouper(指针切片、值切片、命名切片均可),就能按 Key 分组;而[]string、字符串、nil输入或nilKey 都会失败。这与文档"目前仅支持 Pages"的说明完全一致。

基本用法:将任意页面集合打包为一个分组

group最直接的用法是把一个页面集合按你指定的 Key 打包成一个PageGroup。文档给出的示例将站点常规页面切分为"最新 10 篇"与"最早 10 篇"两组:

{{ $new := .Site.RegularPages | first 10 | group "New" }} {{ $old := .Site.RegularPages | last 10 | group "Old" }} {{ $groups := slice $new $old }} {{ range $groups }} <h3>{{ .Key }}{{/* Prints "New", "Old" */}}</h3> <ul> {{ range .Pages }} <li> <a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a> <div class="meta">{{ .Date.Format "Mon, Jan 2, 2006" }}</div> </li> {{ end }} </ul> {{ end }}

这段模板的关键链路是:

  1. .Site.RegularPages | first 10:先取出常规页面集合的前 10 篇,得到一个新的页面 slice;
  2. | group "New":用字符串"New"作为 Key,把这 10 篇打包成一个PageGroup
  3. slice $new $old:把两个PageGroup组装成一个分组列表(PagesGroup);
  4. range $groups迭代每个分组,用.Key访问分组名,用.Pages遍历组内页面,再通过.RelPermalink.LinkTitle.Date.Format渲染列表项。

PageGroup 结构

group返回的类型在 resources/page/pagegroup.go 中定义:

// PageGroup represents a group of pages, grouped by the key. // The key is typically a year or similar. type PageGroup struct { // The key, typically a year or similar. Key any // The Pages in this group. Pages }
  • Key:任意类型的键,模板示例中为字符串"New""Old"
  • Pages:内嵌的Pages类型,因此.Pages直接就是一个完整的页面集合,可以继续使用rangelenwhere等一切页面集合方法。

多个PageGroup组成的分组列表类型为PagesGroup[]PageGroup,见 resources/page/pagegroup.go),它还提供Reverse()方法用于反转分组顺序,以及Len()方法统计所有分组内页面总数。

与内置 group 方法的关系:同一类型,双向打通

文档特别强调:group函数产生的PageGroup与 Hugo 内置 group 方法(如GroupByGroupByDateGroupByParam)返回的分组是同一类型

内置分组方法定义在 resources/page/pagegroup.go 中,例如:

  • Pages.GroupBy(ctx, key, order...):按页面的字段或方法值分组;
  • Pages.GroupByDate(format, order...):按日期字段分组;
  • Pages.GroupByParam(key, order...):按页面 Front Matter 参数分组;
  • 此外还有GroupByPublishDateGroupByExpiryDateGroupByLastmodGroupByParamDate等。

它们返回的都是PagesGroup(即[]PageGroup),与slice $new $old组装出来的结构完全一致。这意味着:

  • group手工打包的分组,与用GroupByDate "Jan 2006"自动按月份生成的分组,可以混用在同一套渲染逻辑中(都通过.Key+.Pages访问);
  • 反过来,内置分组方法得到的结果也直接兼容group相关的一切下游处理(如分页、slice重组)。

这种"函数与方法返回同构类型"的设计,让你可以在手工分组(group)与自动分组(GroupBy*)之间自由切换,渲染模板无需改动。

分组结果也可以分页

文档指出:"The example above can be paginated"(上面的示例可以分页)。由于group的产物是PageGroup/PagesGroup,而 Hugo 的分页机制原生支持分组列表。

Hugo 内置的.Paginate方法可直接接收PagesGroup并对分组进行分页,官方分页文档 docs/content/en/templates/pagination.md 中的分组分页示例如下:

{{ $pages := where site.RegularPages "Type" "posts" }} {{ $paginator := .Paginate ($pages.GroupByDate "Jan 2006") }} {{ range $paginator.PageGroups }} <h2>{{ .Key }}</h2> {{ range .Pages }} <h3><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h3> {{ end }} {{ end }} {{ partial "pagination.html" . }}

将文档中的group示例同样接入分页,可得到"最新 10 篇 / 最早 10 篇 + 每页一个分组"的组合方案:

{{ $new := .Site.RegularPages | first 10 | group "New" }} {{ $old := .Site.RegularPages | last 10 | group "Old" }} {{ $groups := slice $new $old }} {{ $paginator := .Paginate $groups }} {{ range $paginator.PageGroups }} <h3>{{ .Key }}</h3> <ul> {{ range .Pages }} <li> <a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a> <div class="meta">{{ .Date.Format "Mon, Jan 2, 2006" }}</div> </li> {{ end }} </ul> {{ end }} {{ partial "pagination.html" . }}

关于分页,docs/content/en/templates/pagination.md 特别提示了最常见的坑:同一个列表页不要多次调用分页。首次调用会被缓存且不可更改,后续调用都会复用缓存结果,导致行为与预期不符;条件分页时应使用if-else而非compare.Conditional(后者会急切求值两个分支)。

从源码看,分页器内部通过ToPagesGroup(resources/page/pagegroup.go)把[]PageGroupPagesGroup等输入统一转换为PagesGroup再进行分页;如果传入的元素不是PageGroup,会返回明确错误"unsupported type in paginate from slice, got %T instead of PageGroup"。这也印证了:group的产物类型与分页管道是天然兼容的。

典型应用场景

基于上述能力,group在真实站点中常用来实现以下几类需求:

  1. "最新/最旧"区块:如文档示例,用first/last截取页面集合后打上自定义标签分组,一次渲染两个区块;
  2. 自定义分类列表:手工为任意切片(如筛选后的结果集)赋予语义化 Key,再交给通用分组渲染模板;
  3. 分组分页:将手工分组结果直接喂给.Paginate,实现"每页一个分组"的归档页,与GroupByDate等内置方法产出的分组共用同一套PageGroups渲染与分页逻辑;
  4. whereslice等集合函数串联group处于管道末端,输入来自任何可产出Pages的表达式,输出又能继续被slicePaginate消费。

常见错误与注意事项

结合 tpl/collections/collections.go 的源码行为,使用group时有以下边界需要留意:

场景结果
nil作为 Key报错nil is not a valid key to group by
传入非页面类型(如[]string、字符串、数字切片)报错grouping not supported for type ...
传入nil页面集合报错(同上)
空页面集合(如first 0正常返回空分组,len .Pages为 0
页面集合(Pages*tstGrouper等实现Grouper的类型)正常返回PageGroup

文档 Front Matter 中标注的签名collections.Group KEY PAGES、返回类型page.PageGroup以及别名group(历史别名/functions/group)也都与实际源码一一对应,可直接在模板中使用。

小结

collections.Groupgroup)是 Hugo 模板中把任意页面集合"打上自定义标签"的轻量工具:它返回与内置GroupBy*方法完全同构的page.PageGroup,因此既可以独立渲染,也能无缝接入slice重组与.Paginate分页管道。配合first/last/where等集合函数,你可以在不引入任何分类体系的前提下,自由构建"最新与最旧""标签分组""分组归档页"等页面区块。相关实现与测试可进一步参考 tpl/collections/collections.go、resources/page/pagegroup.go 与 tpl/collections/collections_integration_test.go。

【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询