【问题标题】:In a Go template range loop, are variables declared outside the loop reset on each iteration?在 Go 模板范围循环中,循环外声明的变量是否会在每次迭代时重置?
【发布时间】:2015-04-24 19:13:43
【问题描述】:

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

其中.Posts 是一个post 结构数组,每个结构都有一个.Content 和一个.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 的值在每次迭代时都会重置,或许可以提出一种方法来完成我在这里尝试做的事情?

【问题讨论】:

    标签: go go-templates


    【解决方案1】:

    注意: Go 1.11 will support modifying template variables via assignment。这将是有效的代码:

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

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


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

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

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

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

    还有……

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

    您可以为模板注册一个函数(请参阅template.Funcs()),您可以将$index 传递给该函数,它将返回前一个元素的日期字段($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}}
    

    【讨论】:

    • 感谢您的详细解释。由于我对 Go 很陌生,我没有意识到(直到你指出):= 实际上是在重新声明$prevDate。有没有办法在范围循环之外声明的模板本身内修改$prevDate 的值?
    • @DelPutnam 不行,不能修改模板变量,只能重新声明,很多情况下都可以(还是可以同名引用),但是要知道所有(重新)声明都是作用域,它们从(重新)声明点到当前块结束都有效。哲学是 complex 的东西不应该是模板的一部分,而应该在 Go 中实现(并从模板访问或传递给模板)。是的,您可以争辩说更改变量的值并不复杂,但它不会改变您无法修改它们的事实。
    • 这是有道理的。再次感谢您的精彩解释。
    【解决方案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.
    }
    

    并使用 template function 将其挂接到模板中:

    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 }}
    

    playground example

    如果帖子 Date 字段是 time.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),它适用于任意切片类型(与另一个答案中的两个解决方案不同)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-11-29
      • 2014-12-01
      • 1970-01-01
      • 2017-01-15
      • 2012-11-17
      • 1970-01-01
      • 2014-06-15
      相关资源
      最近更新 更多