6

我正在尝试使用在 Go 模板范围循环之外声明的变量来查看上一篇文章是否与当前文章发生在同一天。这是一个简化的例子。

where.Posts是一个 post 结构数组,每个结构都有 a.Content和 a .Date

{{ $prevDate := "" }}
{{ range $post := .Posts }}
    {{ if ne $prevDate $post.Date }}
        <div class="post-date">Posts dated: {{ $post.Date }}</div>
    {{ end }}
    <div class="post-content">{{ $post.Content }}</div>
    {{ $prevDate := $post.Date }}
{{ end }}

问题是它$prevDate似乎""在循环的每次迭代开始时被重置。

任何人都可以帮助我理解为什么$prevDate每次迭代都会重置的值,也许可以提出一种方法来完成我在这里尝试做的事情?

4

2 回答 2

12

注意: Go 1.11 将支持通过 assignment 修改模板变量。这将是有效的代码:

{{ $v := "init" }}
{{ if true }}
  {{ $v = "changed" }}
{{ end }}
v: {{ $v }} {{/* "changed" */}}

Go 1.11 之前的原始答案如下:


变量不会被重置。基本上发生的事情是您$prevDate在循环内重新声明了变量。但它仅在重新声明之后和 . 的结束{{end}}标记之前的范围内{{range}}。因此,当循环的下一次迭代到来时,您只会看到尚未更改的“外部”变量(因为您创建了一个新变量)。

您不能更改您创建的模板变量的值。

例如,您可以使用以下range表格:

{{ range $index, $post := .Posts }}

和...

解决方案 #1:使用已注册的函数

您可以为模板注册一个函数(请参阅template.Funcs()),您可以将其传递给$index它,它将返回前一个元素的日期字段(at $index -1)。

它看起来像这样:

func PrevDate(i int) string {
    if i == 0 {
        return ""
    }
    return posts[i-1].Date
}

// Registering it:
var yourTempl = template.Must(template.New("").
    Funcs(map[string]interface{}{"PrevDate": PrevDate}).
    Parse(yourStringTemplate))

从您的模板中,您可以这样称呼它:

{{range $index, $post := .Posts}}
    {{$prevDate := PrevDate $index}}
{{end}}

解决方案#2:使用帖子方法

这个解决方案是模拟的,但更简单:向你的方法添加一个方法,Posts你可以直接调用它。无需注册功能。

例如:

type Post struct {
    // Your Post type
    Date string
}

type Posts []Post

func (p *Posts) PrevDate(i int) string {
    if i == 0 {
        return ""
    }
    return (*p)[i-1].Date
}

从您的模板中,您可以这样称呼它:

{{range $index, $post := .Posts}}
    {{$prevDate := $.Posts.PrevDate $index}}
{{end}}
于 2015-02-23T13:19:41.273 回答
2

Go 模板并非旨在支持复杂的逻辑。有 Go 编程语言。由于这种理念,模板具有局限性。一个限制是不能更改模板变量。

处理此限制的一种方法是在 Go 中构造数据以匹配输出的结构。创建一个类型来保存日期的帖子并呈现这些类型的一部分。该模板仅包含 PostsForDate 和 Posts。

type PostsForDate struct {
    Date time.Time
    Posts []*Post
}

var Dates []PostsForDate

{{range .Dates}}
    <div class="post-date">Posts dated: {{.Date}}</div>
    {{range .Posts}}
       <div class="post-content">{{.Content}}</div>
    {{end}}
{{end}}

一个更简单的选择(在某种程度上违背了设计理念)是在 Go 中创建一个类型来记录当前值并报告对该值的更改。

type change struct {
    current interface{}
}

func (c *change) Changed(next interface{}) bool {
    result := c.current != next
    c.current = next
    return result
}

func newChange() *change {
    return &change{&struct{ int }{}} // initial value ensures that first change is fired.
}

并使用模板函数将其挂接到模板中:

t := template.Must(template.New("").Funcs(template.FuncMap{"change": newChange}).Parse(` some template `))

在这样的模板中使用它:

{{ $i := change }}
{{ range $post := .Posts }}
    {{ $i.Change $post.Date }}
        <div class="post-date">Posts dated: {{ $post.Date }}</div>
    {{ end }}
    <div class="post-content">{{ $post.Content }}</div>
{{ end }}

游乐场示例

如果帖子Date字段是 atime.Time并且帖子在一天内有不同的时间,则上述内容无法按预期工作。一种解决方法是检查渲染日期的变化(例如$post.Date.Format "2006-01-02")。添加以下方法以简化此操作:

func (c *change) ChangedValue(next interface{}) interface{} {
    if c.current != next {
        c.current = next
        return next
    }
    return nil
}

像这样使用它:

{{ $i := change }}
{{ range $post := .Posts }}
    {{with $i.ChangedValue ($post.Date.Format "2006-01-02")}}
        <div class="post-date">Posts dated: {{.}}</div>
    {{ end }}
    <div class="post-content">{{ $post.Content }}</div>
{{ end }}

这仅在模板包保证这些值被认为是真实的情况下才有效。

此解决方案不需要在每次使用时解析模板(如另一个答案中的解决方案#1),它适用于任意切片类型(与另一个答案中的两个解决方案不同)。

于 2017-12-18T01:38:34.530 回答