Hugo 菜单遍历方法全解析:ByName、ByWeight、Limit、Reverse 实战指南
【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugo
Hugo 的Menu类型提供了一组用于遍历菜单条目(menu entries)的排序与截取方法。本文基于 Hugo 官方文档的 Menu methods 章节,结合 navigation 包 的源码实现,系统讲解ByName、ByWeight、Limit、Reverse四个方法的用途、排序规则、模板写法与源码原理,读完即可在导航栏、页脚或面包屑等模板中熟练控制菜单条目的展示顺序与数量。
方法与排序概览
在 Hugo 中,Menu是一个菜单条目的集合,定义于 navigation/menu.go:
// Menu is a collection of menu entries. type Menu []*MenuEntry // Menus is a dictionary of menus. type Menus map[string]Menu每个MenuEntry包含标识符(Identifier)、名称(Name)、父级(Parent)、权重(Weight)、链接地址(URL/PageRef)、标题(Title)以及可选的Pre、Post前后缀 HTML 与自定义Params,具体定义见 MenuConfig 结构体。
四个方法的签名与作用如下:
| 方法 | 签名 | 作用 | 返回类型 |
|---|---|---|---|
ByName | MENU.ByName | 按条目name排序 | navigation.Menu |
ByWeight | MENU.ByWeight | 按weight、再按name、再按identifier排序(默认排序) | navigation.Menu |
Limit | MENU.Limit N | 仅返回前 N 个条目 | navigation.Menu |
Reverse | MENU.Reverse | 反转条目的当前排序顺序 | navigation.Menu |
这些方法不修改原始菜单,而是返回一个新的有序(或截取后的)菜单副本,因此可以在range中安全地链式调用,例如.Site.Menus.main.ByName.Limit 2。
ByName:按名称排序
ByName方法返回按name排序的菜单条目。考虑如下菜单定义:
# hugo.toml [[menus.main]] name = 'Services' pageRef = '/services' weight = 10 [[menus.main]] name = 'About' pageRef = '/about' weight = 20 [[menus.main]] name = 'Contact' pageRef = '/contact' weight = 30在模板中按name排序遍历:
<ul> {{ range .Site.Menus.main.ByName }} <li><a href="{{ .URL }}">{{ .Name }}</a></li> {{ end }} </ul>Hugo 渲染结果为:
<ul> <li><a href="/about/">About</a></li> <li><a href="/contact">Contact</a></li> <li><a href="/services/">Services</a></li> </ul>从源码看,ByName的实现通过比较m1.Name与m2.Name来决定顺序(使用compare.LessStrings),见 navigation/menu.go:
// ByName sorts the menu by the name defined in the menu configuration. func (m Menu) ByName() Menu { const key = "menuSort.ByName" title := func(m1, m2 *MenuEntry) bool { return compare.LessStrings(m1.Name, m2.Name) } menus, _ := smc.get(key, menuEntryBy(title).Sort, m) return menus }使用 sort 函数的替代方案
你也可以使用sort函数 来排序菜单条目。例如按name降序排列:
<ul> {{ range sort .Site.Menus.main "Name" "desc" }} <li><a href="{{ .URL }}">{{ .Name }}</a></li> {{ end }} </ul>使用sort函数配合菜单条目时,可以指定以下任一键:Identifier、Name、Parent、Post、Pre、Title、URL或Weight。这些键对应 MenuConfig 中的字段名。
ByWeight:默认排序规则
ByWeight方法返回按weight、再按name、再按identifier排序的菜单条目。这是 Hugo 菜单的默认排序顺序——即使不显式调用任何排序方法,Hugo 也会按此规则渲染菜单。
考虑如下带identifier的菜单定义:
# hugo.toml [[menus.main]] identifier = 'about' name = 'About' pageRef = '/about' weight = 20 [[menus.main]] identifier = 'services' name = 'Services' pageRef = '/services' weight = 10 [[menus.main]] identifier = 'contact' name = 'Contact' pageRef = '/contact' weight = 30在模板中按weight、name、identifier依次排序:
<ul> {{ range .Site.Menus.main.ByWeight }} <li><a href="{{ .URL }}">{{ .Name }}</a></li> {{ end }} </ul>Hugo 渲染结果为:
<ul> <li><a href="/services/">Services</a></li> <li><a href="/about/">About</a></li> <li><a href="/contact">Contact</a></li> </ul>[!NOTE] 在上面的菜单定义中,
identifier属性只有在两个或更多菜单条目具有相同name,或需要使用翻译表本地化名称时才必须提供。
源码中的默认排序比较器完整展现了"先 weight、再 name、再 identifier"的优先级,见 navigation/menu.go:
var defaultMenuEntrySort = func(m1, m2 *MenuEntry) bool { if m1.Weight == m2.Weight { c := compare.Strings(m1.Name, m2.Name) if c == 0 { return m1.Identifier < m2.Identifier } return c < 0 } if m2.Weight == 0 { return true } if m1.Weight == 0 { return false } return m1.Weight < m2.Weight }一个容易忽略的细节是:weight 为 0 的条目会被排到所有带 weight 的条目之后(源码中m2.Weight == 0返回true、m1.Weight == 0返回false的两个分支保证了这一点)。因此若希望某个条目固定在列表最前,应给它赋一个正权重;而不设置 weight 的条目默认按 weight=0 处理、排在末尾。ByWeight方法本身通过缓存调用此默认排序器,见 navigation/menu.go。
使用 sort 函数的替代方案
同样可以使用sort函数按weight降序排列:
<ul> {{ range sort .Site.Menus.main "Weight" "desc" }} <li><a href="{{ .URL }}">{{ .Name }}</a></li> {{ end }} </ul>可指定的键与ByName一节相同:Identifier、Name、Parent、Post、Pre、Title、URL或Weight。
Limit:截取前 N 个条目
Limit方法返回给定的菜单,仅保留前 N 个条目。它常与排序方法链式使用——先排序、再截取,以得到"权重最高的前 N 项"之类的效果。
仍以上一节的菜单定义(Services/About/Contact)为例,先按名称排序、再只取前 2 项:
<ul> {{ range .Site.Menus.main.ByName.Limit 2 }} <li><a href="{{ .URL }}">{{ .Name }}</a></li> {{ end }} </ul>Hugo 渲染结果为:
<ul> <li><a href="/about/">About</a></li> <li><a href="/contact">Contact</a></li> </ul>源码实现非常直接,见 navigation/menu.go:
// Limit limits the returned menu to n entries. func (m Menu) Limit(n int) Menu { if len(m) > n { return m[0:n] } return m }当菜单条目数不超过 N 时,Limit原样返回整个菜单,不会报错也不会补空;当条目数超过 N 时,通过切片m[0:n]截取前 N 项。实际项目中可用它实现"只显示最新/最靠前的 5 个导航链接"等场景。
Reverse:反转排序顺序
Reverse方法返回反转排序顺序后的菜单,通常与ByName、ByWeight等排序方法链式调用,以获得降序效果。
以同名菜单定义为例,先按名称升序、再反转,即得到按名称降序的结果:
<ul> {{ range .Site.Menus.main.ByName.Reverse }} <li><a href="{{ .URL }}">{{ .Name }}</a></li> {{ end }} </ul>Hugo 渲染结果为:
<ul> <li><a href="/services/">Services</a></li> <li><a href="/contact">Contact</a></li> <li><a href="/about/">About</a></li> </ul>源码实现采用经典的双指针原地交换,见 navigation/menu.go:
// Reverse reverses the order of the menu entries. func (m Menu) Reverse() Menu { const key = "menuSort.Reverse" reverseFunc := func(menu Menu) { for i, j := 0, len(menu)-1; i < j; i, j = i+1, j-1 { menu[i], menu[j] = menu[j], menu[i] } } menus, _ := smc.get(key, reverseFunc, m) return menus }Reverse是对当前顺序的整体反转,而非"按某个字段降序排序"。因此它的语义取决于前置排序:ByName.Reverse是名称降序,ByWeight.Reverse则是 weight 降序。若菜单尚未排序,Reverse反转的就是配置中的原始声明顺序。
链式调用与缓存机制
四个方法均可任意组合,形成"排序 → 截取 → 反转"的链式调用,例如:
<!-- 按 weight 升序取前 3 项后再反转(实际得到 weight 较大的前 3 项) --> <ul> {{ range .Site.Menus.main.ByWeight.Limit 3.Reverse }} <li><a href="{{ .URL }}">{{ .Name }}</a></li> {{ end }} </ul>从源码可以观察到,ByName、ByWeight、Reverse三个方法都通过smc.get(key, ...)获取结果,smc是一个包级共享的菜单缓存实例(var smc = newMenuCache(),见 navigation/menu.go)。缓存实现在 navigation/menu_cache.go 中:它以方法名(如"menuSort.ByName")为键、以传入的原始菜单列表为匹配条件,命中时直接返回已排序的结果,未命中时才执行排序并写入缓存。这意味着在同一个页面构建周期内多次调用ByName/ByWeight/Reverse不会重复排序,对大型站点的高频导航渲染是重要的性能保障。
小结
ByWeight是 Hugo 菜单的默认排序:先weight,再name,最后identifier;weight 为 0 的条目排末尾。ByName忽略 weight,仅按名称排序;需要降序时配合Reverse或sort函数。Limit N截取前 N 项,常与排序方法链式使用;Reverse整体反转当前顺序。- 使用
sort函数时,可指定的排序键为:Identifier、Name、Parent、Post、Pre、Title、URL、Weight。 - 四个方法的实现与默认排序器均位于 navigation/menu.go,排序结果缓存逻辑见 navigation/menu_cache.go,相关行为可参考 navigation 包测试。
掌握这四个方法,即可在 Hugo 模板中精确控制导航菜单的显示顺序与数量,无需引入任何额外依赖或自定义排序逻辑。
【免费下载链接】hugoThe world’s fastest framework for building websites.项目地址: https://gitcode.com/gh_mirrors/hu/hugo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考