Hugo 静态站点生成器中的上下文和变量
已发表: 2022-03-10在本文中,我们将仔细研究上下文在 Hugo 静态站点生成器中的工作原理。 我们将研究数据如何从内容流向模板,某些构造如何改变可用数据,以及我们如何将这些数据传递给部分模板和基本模板。
本文不是对 Hugo 的介绍。 如果您对 Hugo 有一些经验,您可能会从中获得最大收益,因为我们不会从头开始讨论每个概念,而是专注于上下文和变量的主要主题。 但是,如果您自始至终参考 Hugo 文档,即使没有以前的经验,您也可能能够跟随!
我们将通过建立一个示例页面来研究各种概念。 不会详细介绍示例站点所需的每个文件,但完整的项目可在 GitHub 上找到。 如果您想了解这些部分是如何组合在一起的,那么这是一个很好的起点。 另请注意,我们不会介绍如何设置Hugo 站点或运行开发服务器 - 运行示例的说明在存储库中。
什么是静态站点生成器?
如果您对静态站点生成器的概念不熟悉,这里有一个快速介绍! 通过将静态站点生成器与动态站点进行比较,也许可以更好地描述静态站点生成器。 像 CMS 这样的动态站点通常会为每次访问从头开始组装一个页面,可能会从数据库中获取数据并组合各种模板来完成此操作。 在实践中,缓存的使用意味着页面不会经常重新生成,但为了进行比较,我们可以这样认为。 动态站点非常适合动态内容:经常更改的内容、根据输入以多种不同配置呈现的内容以及站点访问者可以操作的内容。
相比之下,许多网站很少改变并且很少接受访问者的输入。 应用程序的“帮助”部分、文章列表或电子书可能是此类站点的示例。 在这种情况下,更有意义的是在内容更改时将最终页面组装一次,然后为每个访问者提供相同的页面,直到内容再次更改。
动态站点具有更大的灵活性,但对运行它们的服务器提出了更高的要求。 它们也可能难以在地理上分布,尤其是在涉及数据库的情况下。 静态站点生成器可以托管在任何能够提供静态文件的服务器上,并且易于分发。
今天,混合这些方法的常见解决方案是 JAMstack。 “JAM”代表 JavaScript、API 和标记,描述了 JAMstack 应用程序的构建块:静态站点生成器生成静态文件以交付给客户端,但堆栈具有运行在客户端上的 JavaScript 形式的动态组件 —然后,此客户端组件可以使用 API 向用户提供动态功能。
雨果
Hugo 是一个流行的静态站点生成器。 它是用 Go 编写的,Go 是一种编译型编程语言这一事实暗示了 Hugos 的一些优点和缺点。 一方面,Hugo非常快,这意味着它可以非常快速地生成静态站点。 当然,这与使用 Hugo 创建的网站对于最终用户的速度有多快或多慢没有关系,但对于开发人员来说,Hugo 在眨眼之间就可以编译出大型网站这一事实是非常有价值的。
但是,由于 Hugo 是用编译语言编写的,因此很难对其进行扩展。 其他一些站点生成器允许您将自己的代码(使用 Ruby、Python 或 JavaScript 等语言)插入到编译过程中。 要扩展 Hugo,您需要将代码添加到 Hugo 本身并重新编译它- 否则,您会被 Hugo 开箱即用的模板函数所困扰。
虽然它确实提供了丰富多样的功能,但如果您的页面的生成涉及一些复杂的逻辑,这一事实可能会受到限制。 正如我们发现的那样,如果一个网站最初是在动态平台上运行的,那么您理所当然地认为可以放入自定义代码的情况确实会越来越多。
我们的团队维护着与我们的主要产品 Tower Git 客户端相关的各种网站,我们最近考虑将其中一些网站转移到静态网站生成器中。 我们的一个网站,“学习”网站,看起来特别适合试点项目。 该站点包含各种免费学习材料,包括视频、电子书和 Git 常见问题解答,以及其他技术主题。
它的内容主要是静态的,并且任何交互功能(如通讯注册)都已经由 JavaScript 提供支持。 2020 年底,我们将该站点从之前的 CMS 转换为 Hugo ,今天它作为静态站点运行。 自然地,我们在这个过程中学到了很多关于 Hugo 的知识。 这篇文章是分享我们学到的一些东西的一种方式。
我们的例子
由于本文源于我们将页面转换为 Hugo 的工作,因此将一个(非常!)简化的假设登录页面作为示例似乎很自然。 我们的主要关注点将是一个可重用的所谓“列表”模板。
简而言之,Hugo 将对包含子页面的任何页面使用列表模板。 Hugos 模板层次结构远不止这些,但您不必实现所有可能的模板。 单个列表模板有很长的路要走。 它将用于任何需要列表模板但没有更多专用模板可用的情况。
潜在的用例包括主页、博客索引或常见问题解答列表。 我们的可重用列表模板将驻留在项目的layouts/_default/list.html
中。 同样,编译我们的示例所需的其余文件都可以在 GitHub 上找到,您还可以在其中更好地了解这些部分是如何组合在一起的。 GitHub 存储库还附带了一个single.html
模板——顾名思义,这个模板用于没有子页面的页面,但它们本身就是单个内容。
现在我们已经搭建好了舞台并解释了我们将要做什么,让我们开始吧!
上下文或“点”
一切都从点开始。 在 Hugo 模板中,对象.
——“点”——指的是当前的上下文。 这是什么意思? Hugo 中呈现的每个模板都可以访问一组数据——它的上下文。 这最初设置为表示当前正在呈现的页面的对象,包括其内容和一些元数据。 上下文还包括站点范围的变量,例如配置选项和有关当前环境的信息。 您将使用.Title
访问当前页面的标题等字段,并通过.Hugo.Version
访问正在使用的 Hugo 版本 - 换句话说,您正在访问.
结构体。
重要的是,这种上下文可能会发生变化,使上面的“.Title”之类的引用指向其他内容,甚至使其无效。 例如,当您使用range
遍历某种集合时,或者当您将模板拆分为 partials 和 base templates时,就会发生这种情况。 稍后我们将详细了解这一点!
Hugo 使用 Go 的“模板”包,所以当我们在本文中提到 Hugo 模板时,我们实际上是在谈论 Go 模板。 Hugo 确实添加了很多标准 Go 模板中没有的模板功能。
在我看来,上下文和重新绑定它的可能性是 Hugo 的最佳功能之一。 对我来说,总是让“点”代表模板在某个特定点的主要焦点的任何对象是很有意义的,并在我继续进行时根据需要重新绑定它。 当然,也有可能让自己陷入混乱,但到目前为止我对它很满意,以至于我很快就开始在我看过的任何其他静态站点生成器中错过它。
有了这个,我们已经准备好看看我们示例的简单起点——下面的模板,位于我们项目的位置layouts/_default/list.html
中:
<html> <head> <title>{{ .Title }} | {{ .Site.Title }}</title> <link rel="stylesheet" href="/css/style.css"> </head> <body> <nav> <a class="logo" href="{{ "/" | relURL }}"> <img src="/img/tower-logo.svg"> <img src="/img/tower-claim.svg"> </a> <ul> <li><a href="/">Home</a></li> </ul> </nav> <section class="content"> <div class="container"> <h1>{{ .Title }}</h1> {{ .Content }} </div> </section> </body> </html>
大部分模板由基本的 HTML 结构、样式表链接、导航菜单和一些用于样式的额外元素和类组成。 有趣的东西在花括号之间,这表明 Hugo 介入并发挥其魔力,用评估某些表达式的结果替换大括号之间的任何内容,并可能操纵上下文。
你可能猜到了,标题标签中的 {{ .Title {{ .Title }}
} 指的是当前页面的标题,而{{ .Site.Title }}
指的是整个站点的标题,在 Hugo 配置中设置. 像{{ .Title }}
这样的标签只是告诉 Hugo 用当前上下文中字段Title
的内容替换该标签。
因此,我们在模板中访问了属于该页面的一些数据。 这些数据来自哪里? 这就是下一节的主题。
内容和前沿
上下文中可用的一些变量由 Hugo 自动提供。 其他的都是我们定义的,主要是在内容文件中。 还有其他数据源,如配置文件、环境变量、数据文件甚至 API。 在本文中,我们将重点关注作为数据源的内容文件。
通常,单个内容文件代表单个页面。 典型的内容文件包括该页面的主要内容,还包括有关该页面的元数据,例如其标题或创建日期。 Hugo 支持主要内容和元数据的多种格式。 在本文中,我们可能会使用最常见的组合:内容以 Markdown 形式提供在包含元数据的文件中,作为 YAML 前端。
在实践中,这意味着内容文件以一个部分开头,该部分由每端包含三个破折号的行分隔。 这部分构成了前端,这里元数据使用key: value
语法定义(我们很快就会看到,YAML 也支持更复杂的数据结构)。 前面的内容是使用 Markdown 标记语言指定的实际内容。
让我们通过一个例子来使事情更具体。 这是一个非常简单的内容文件,其中包含一个前端字段和一段内容:
--- title: Home --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!
(此文件位于我们项目中的content/_index.md
中, _index.md
表示具有子页面的页面的内容文件。同样,GitHub 存储库清楚地说明了哪个文件应该去哪里。)
使用之前的模板以及一些样式和外围文件(都可以在 GitHub 上找到)进行渲染,结果如下所示:
您可能想知道我们的内容文件前面的字段名称是否是预先确定的,或者我们是否可以添加任何我们喜欢的字段。 答案是“两者”。 有一个预定义字段列表,但我们也可以添加我们能想到的任何其他字段。 但是,这些字段在模板中的访问方式略有不同。 虽然像title
这样的预定义字段可以简单地作为.Title
访问,但像author
这样的自定义字段是使用.Params.author
访问的。
(有关预定义字段的快速参考,以及函数、函数参数和页面变量等内容,请参阅我们自己的 Hugo 备忘单!)
.Content
变量,用于访问模板中内容文件的主要内容,是特殊的。 Hugo 有一个“短代码”功能,允许您在 Markdown 内容中使用一些额外的标签。 您也可以定义自己的。 不幸的是,这些简码只能通过.Content
变量起作用——虽然您可以通过 Markdown 过滤器运行任何其他数据,但这不会处理内容中的简码。
关于未定义变量的注意事项:访问像.Date
这样的预定义字段始终有效,即使您没有设置它——在这种情况下将返回一个空值。 访问未定义的自定义字段,如.Params.thisHasNotBeenSet
也可以,返回一个空值。 但是,访问像.thisDoesNotExist
这样的非预定义顶级字段将阻止站点编译。
正如前面.Params.author
以及.Hugo.version
和.Site.title
所指出的,链式调用可用于访问嵌套在其他数据结构中的字段。 我们可以在前面的内容中定义这样的结构。 让我们看一个例子,我们定义一个地图或字典,在我们的内容文件中为页面上的横幅指定一些属性。 这是更新后的content/_index.md
:
--- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!
现在,让我们在模板中添加一个横幅,使用.Params
以上述方式引用横幅数据:
<html> ... <body> ... <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> </body> </html>
这是我们的网站现在的样子:
好的! 目前,我们正在访问默认上下文的字段,没有任何问题。 但是,如前所述,此上下文不是固定的,而是可以更改的。
让我们来看看这可能是如何发生的。
流量控制
流控制语句是模板语言的重要组成部分,它允许您根据变量的值、循环数据等执行不同的操作。 Hugo 模板提供了预期的构造集,包括用于条件逻辑的if/else
和用于循环的range
。 在这里,我们不会笼统地介绍 Hugo 中的流控制(有关更多信息,请参阅文档),而是关注这些语句如何影响上下文。 在这种情况下,最有趣的语句是with
和range
。
让我们从with
。 该语句检查某个表达式是否具有“非空”值,如果有,则重新绑定上下文以引用该表达式的值。 end
标记表示with
语句的影响停止的点,并且上下文被反弹到之前的任何位置。 Hugo 文档将非空值定义为 false、0 以及任何长度为零的数组、切片、映射或字符串。
目前,我们的列表模板根本没有做太多的列表。 列表模板以某种方式实际展示其某些子页面可能是有意义的。 这为我们提供了一个很好的机会来举例说明我们的流控制语句。
也许我们想在页面顶部显示一些特色内容。 这可以是任何内容——例如博客文章、帮助文章或食谱。 现在,假设我们的 Tower 示例站点有一些页面突出了它的特性、用例、帮助页面、博客页面和“学习平台”页面。 这些都位于content/
目录中。 我们通过在主页的内容文件中添加一个字段来配置要显示的内容, content/_index.md
。 该页面由其路径引用,假设内容目录为根目录,如下所示:
--- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days without limitations featured: /features.md ... --- ...
接下来,必须修改我们的列表模板以显示该内容。 Hugo 有一个模板函数.GetPage
,它允许我们引用当前正在渲染的页面对象以外的页面对象。 回想一下上下文, .
, 最初是否绑定到表示正在呈现的页面的对象? 使用.GetPage
和with
,我们可以暂时将上下文重新绑定到另一个页面,在显示我们的特色内容时引用该页面的字段:
<nav> ... </nav> <section class="featured"> <div class="container"> {{ with .GetPage .Params.featured }} <article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} </div> </section>
在这里, with
和end
标签之间的{{ .Title }}
、 {{ .Summary }}
和{{ .Permalink }}
指的是特色页面中的那些字段,而不是正在呈现的主要字段。
除了有特色内容之外,让我们在下面列出更多内容。 就像特色内容一样,列出的内容片段将在我们主页的内容文件content/_index.md
中定义。 我们将像这样将内容路径列表添加到我们的前端(在这种情况下还指定部分标题):
--- ... listing_headline: Featured Pages listing: - /help.md - /use-cases.md - /blog/_index.md - /learn.md ---
博客页面有自己的目录和_index.md
文件的原因是博客会有自己的子页面——博客文章。
为了在我们的模板中显示这个列表,我们将使用range
。 不出所料,该语句将遍历一个列表,但它也会依次将上下文重新绑定到列表的每个元素。 这对我们的内容列表非常方便。
注意,从 Hugo 的角度来看,“listing”只包含一些字符串。 对于“范围”循环的每次迭代,上下文将绑定到其中一个字符串。 为了访问实际的页面对象,我们将其路径字符串(现在是.
的值)作为参数提供给.GetPage
。 然后,我们将再次使用with
语句将上下文重新绑定到列出的页面对象,而不是其路径字符串。 现在,很容易依次显示每个列出的页面的内容:
<aside> ... </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} <article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} {{ end }} </div> </div> </section>
这是该网站此时的样子:
但是等一下,上面的模板中有一些奇怪的东西——我们不是调用.GetPage
,而是调用$.GetPage
。 你能猜到为什么.GetPage
不起作用吗?
符号.GetPage
表示GetPage
函数是当前上下文的方法。 确实,在默认上下文中,有这样一个方法,但我们只是继续前进并更改了上下文! 当我们调用.GetPage
时,上下文被绑定到一个没有那个方法的字符串。 我们解决这个问题的方法是下一节的主题。
全球背景
如上所示,在某些情况下上下文已更改,但我们仍希望访问原始上下文。 在这里,这是因为我们想要调用一个存在于原始上下文中的方法——另一种常见的情况是当我们想要访问正在呈现的主页的某些属性时。 没问题,有一个简单的方法可以做到这一点。
在 Hugo 模板中, $
称为全局上下文,指的是上下文的原始值——模板处理开始时的上下文。 在上一节中,它用于调用.GetPage
方法,即使我们已将上下文重新绑定到字符串。 现在,我们还将使用它来访问正在呈现的页面的字段。
在本文开头,我提到我们的列表模板是可重用的。 到目前为止,我们只将它用于主页,呈现位于content/_index.md
的内容文件。 在示例存储库中,还有另一个将使用此模板呈现的内容文件: content/blog/_index.md
。 这是博客的索引页面,就像主页一样,它显示了一个特色内容并列出了更多——在这种情况下是博客文章。
现在,假设我们想在主页上显示稍微不同的列出的内容——这不足以保证一个单独的模板,但我们可以在模板本身中使用条件语句来做一些事情。 例如,如果我们检测到我们正在呈现主页,我们将在两列网格中显示列出的内容,而不是单列列表。
Hugo 带有一个页面方法, .IsHome
,它提供了我们需要的功能。 当我们发现我们在主页上时,我们将通过向各个内容片段添加一个类来处理表示中的实际变化,让我们的 CSS 文件完成剩下的工作。
当然,我们可以将类添加到 body 元素或一些包含元素,但这不能很好地演示全局上下文。 当我们为列出的内容编写 HTML 时, .
指的是列出的页面,但需要在正在呈现的主页上调用IsHome
。 全球背景来拯救我们:
<section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} <article{{ if $.IsHome }} class="home"{{ end }}> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} {{ end }} </div> </div> </section>
博客索引看起来就像我们的主页一样,尽管内容不同:
…但是我们的主页现在以网格形式显示其特色内容:
部分模板
在建立一个真实的网站时,将您的模板分成几部分很快就会变得有用。 也许您想重用模板的某些特定部分,或者您只是想将一个庞大、笨重的模板拆分成连贯的部分。 为此,Hugo 的部分模板是可行的方法。
从上下文的角度来看,这里重要的是,当我们包含一个部分模板时,我们明确地将我们想要提供给它的上下文传递给它。 一种常见的做法是在包含部分时传递上下文,如下所示: {{ partial "my/partial.html" . }}
{{ partial "my/partial.html" . }}
。 如果这里的点是指正在呈现的页面,那将是传递给部分的; 如果上下文已经反弹到其他东西,那就是传递下来的东西。
当然,您可以像在普通模板中一样在部分模板中重新绑定上下文。 在这种情况下,全局上下文$
是指传递给部分的原始上下文,而不是正在呈现的主页面(除非这是传入的内容)。
如果我们希望部分模板能够访问某些特定的数据,如果我们只将它传递给部分模板,我们可能会遇到问题。 还记得我们之前在重新绑定上下文后访问页面方法的问题吗? partials也是如此,但在这种情况下,全局上下文无法帮助我们——如果我们将字符串传递给部分模板,则部分中的全局上下文将引用该字符串,我们赢了'不能调用页面上下文中定义的方法。
解决这个问题的方法是在包含部分时传入多条数据。 但是,我们只允许为部分调用提供一个参数。 但是,我们可以将此参数设为复合数据类型,通常是映射(在其他编程语言中称为字典或散列)。
例如,在此映射中,我们可以将Page
键设置为当前页面对象,以及用于传入任何自定义数据的其他键。然后页面对象将作为部分中的.Page
可用,而另一个类似地访问地图的值。 使用dict
模板函数创建映射,该函数接受偶数个参数,交替解释为键、其值、键、其值等。
在我们的示例模板中,让我们将特色和列出内容的代码移动到部分中。 对于特色内容,传入特色页面对象就足够了。 但是,除了要呈现的特定列出的内容之外,列出的内容还需要访问.IsHome
方法。 如前所述,虽然.IsHome
在列出的页面的页面对象上也可用,但这不会给我们正确的答案——我们想知道正在呈现的主页是否是主页。
相反,我们可以将一个布尔集传递给调用.IsHome
的结果,但将来部分可能需要访问其他页面方法——让我们传入主页对象以及列出的页面对象。 在我们的示例中,主页位于$
中,列出的页面位于.
. 因此,在传递给listed
的部分的映射中,键Page
获取值$
而键“Listed”获取值.
. 这是更新后的主模板:
<body> <nav> <a class="logo" href="{{ "/" | relURL }}"> <img src="/img/tower-logo.svg"> <img src="/img/tower-claim.svg"> </a> <ul> <li><a href="/">Home</a></li> <li><a href="/blog/">Blog</a></li> </ul> </nav> <section class="featured"> <div class="container"> {{ with .GetPage .Params.featured }} {{ partial "partials/featured.html" . }} {{ end }} </div> </section> <section class="content"> <div class="container"> <h1>{{ .Title }}</h1> {{ .Content }} </div> </section> <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ partial "partials/listed.html" (dict "Page" $ "Listed" .) }} {{ end }} {{ end }} </div> </div> </section> </body>
与列表模板的一部分相比,我们的“精选”部分内容没有变化:
<article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article>
然而,我们对列出的内容的部分反映了这样一个事实,即原始页面对象现在位于.Page
中,而列出的内容片段位于.Listed
中:
<article{{ if .Page.IsHome }} class="home"{{ end }}> <h2>{{ .Listed.Title }}</h2> {{ .Listed.Summary }} <p><a href="{{ .Listed.Permalink }}">Read more →</a></p> </article>
Hugo 还提供了基本模板功能,可以让你扩展一个通用的基本模板,而不是包含子模板。 在这种情况下,上下文的工作方式类似:在扩展基本模板时,您提供的数据将构成该模板中的原始上下文。
自定义变量
也可以在 Hugo 模板中分配和重新分配您自己的自定义变量。 这些将在声明它们的模板中可用,但除非我们明确传递它们,否则它们不会进入任何部分或基本模板。 在“块”内声明的自定义变量(如if
语句指定的变量)仅在该块内可用 - 如果我们想在块外引用它,我们需要在块外声明它,然后在块内修改它根据需要阻止。
自定义变量的名称以美元符号 ( $
)为前缀。 要声明一个变量并同时给它一个值,请使用:=
运算符。 对变量的后续赋值使用=
运算符(不带冒号)。 一个变量在被声明之前不能被赋值,也不能在没有给它一个值的情况下被声明。
自定义变量的一个用例是通过将一些中间结果分配给适当命名的变量来简化长函数调用。 例如,我们可以将特色页面对象分配给名为$featured
的变量,然后将该变量提供给with
语句。 我们还可以将要提供给“列出的”部分的数据放在一个变量中,并将其提供给部分调用。
以下是我们的模板在进行这些更改后的样子:
<section class="featured"> <div class="container"> {{ $featured := .GetPage .Params.featured }} {{ with $featured }} {{ partial "partials/featured.html" . }} {{ end }} </div> </section> <section class="content"> ... </section> <aside> ... </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ $context := (dict "Page" $ "Listed" .) }} {{ partial "partials/listed.html" $context }} {{ end }} {{ end }} </div> </div> </section>
根据我对 Hugo 的经验,我建议您在尝试在模板中实现一些更复杂的逻辑时自由地使用自定义变量。 虽然尝试保持代码简洁是很自然的,但这很容易使事情变得不那么清晰,从而使您和其他人感到困惑。
相反,为每个步骤使用描述性命名的变量,不要担心使用两行(或三行或四行等)来做。
。刮
最后,让我们介绍一下.Scratch
机制。 在早期版本的 Hugo 中,自定义变量只能分配一次; 无法重新定义自定义变量。 如今,可以重新定义自定义变量,这使得.Scratch
不那么重要,尽管它仍然有它的用途。
简而言之, .Scratch
是一个允许您设置和修改自己的变量(如自定义变量)的暂存区。 与自定义变量不同, .Scratch
属于页面上下文,因此将上下文传递给部分,例如,将自动带上暂存变量。
您可以通过调用其方法Set
和Get
来设置和检索.Scratch
上的变量。 还有比这些更多的方法,例如设置和更新复合数据类型,但这两种方法足以满足我们的需求。 Set
有两个参数:要设置的数据的键和值。 Get
只需要一个:您要检索的数据的密钥。
之前,我们使用dict
创建了一个 map 数据结构,将多条数据传递给一个 partial。 这样做是为了让列出的页面的部分可以访问原始页面上下文和特定的列出页面对象。 使用.Scratch
不一定是更好或更坏的方式来做到这一点 - 哪个更可取可能取决于具体情况。
让我们看看使用.Scratch
而不是dict
将数据传递给部分的列表模板会是什么样子。 我们调用$.Scratch.Get
(再次使用全局上下文)将暂存变量“listed”设置为.
- 在这种情况下,列出的页面对象。 然后我们只将页面对象$
传递给部分。 临时变量将自动跟随。
<section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ $.Scratch.Set "listed" . }} {{ partial "partials/listed.html" $ }} {{ end }} {{ end }} </div> </div> </section>
这也需要对listed.html
部分进行一些修改——原始页面上下文现在可以作为“点”使用,而列出的页面是从.Scratch
对象中检索的。 我们将使用自定义变量来简化对所列页面的访问:
<article{{ if .IsHome }} class="home"{{ end }}> {{ $listed := .Scratch.Get "listed" }} <h2>{{ $listed.Title }}</h2> {{ $listed.Summary }} <p><a href="{{ $listed.Permalink }}">Read more →</a></p> </article>
以这种方式做事的一个论据是一致性。 使用.Scratch
,您可以养成始终将当前页面对象传递给任何部分的习惯,将任何额外的数据添加为临时变量。 然后,无论何时您编写或编辑您的分部,您都知道.
是一个页面对象。 当然,您也可以使用传入的映射为自己建立一个约定:例如,始终将页面对象作为.Page
发送。
结论
当涉及到上下文和数据时,静态站点生成器既有好处也有局限性。 一方面,在每次页面访问时运行效率太低的操作在页面编译时只运行一次可能会非常好。 另一方面,即使在主要是静态站点上,访问网络请求的某些部分的频率也会让您感到惊讶。
要处理查询字符串参数,例如,在静态站点上,您必须求助于 JavaScript 或一些专有解决方案,如 Netlify 的重定向。 这里的重点是,虽然从动态站点跳转到静态站点在理论上很简单,但确实需要转变思维方式。 一开始,很容易回到你的旧习惯,但练习会变得完美。
至此,我们结束了对 Hugo 静态站点生成器中的数据管理的了解。 尽管我们只关注其功能的一小部分,但肯定有一些我们没有涵盖的东西本来可以包括在内。 Nevertheless, I hope this article gave you some added insight into how data flows from content files, to templates, to subtemplates and how it can be modified along the way.
Note : If you already have some Hugo experience, we have a nice resource for you, quite appropriately residing on our aforementioned, Hugo-driven “Learn” site! When you just need to check the order of the arguments to the replaceRE
function, how to retrieve the next page in a section, or what the “expiration date” front matter field is called, a cheat sheet comes in handy. We've put together just such a reference, so download a Hugo cheat sheet, in a package also featuring a host of other cheat sheets on everything from Git to the Visual Studio Code editor.
延伸阅读
If you're looking for more information on Hugo, here are some nice resources:
- The official Hugo documentation is always a good place to start!
- A great series of in-depth posts on Hugo on Regis Philibert's blog.