在 Go 模板范围循环中,是否在每次迭代时重置循环外声明的变量?
In a Go template range loop, are variables declared outside the loop reset on each iteration?
我正在尝试使用在 Go 模板范围循环外声明的变量来查看前一个 post 是否与当前 post 发生在同一天。这是一个简化的例子。
其中 .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 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}}
标记之前的范围内。因此,当循环的下一次迭代到来时,您只会看到未更改的 "outer" 变量(因为您创建了一个新变量)。
您不能更改您创建的模板变量的值。
例如,您可以使用以下 range
形式:
{{ range $index, $post := .Posts }}
还有...
解决方案 #1:使用已注册的函数
并且您可以为模板注册一个函数(参见 template.Funcs()
),您可以将 $index
传递给该函数,它将 return 前一个元素的日期字段(位于$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 模板不支持复杂的逻辑。为此,有 Go 编程语言。由于这种理念,模板具有局限性。一项限制是无法更改模板变量。
处理此限制的一种方法是在 Go 中构建数据以匹配输出结构。创建一个类型来保存日期的 posts 并呈现这些类型的一部分。该模板仅包含 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 }}
如果 post Date
字段是 time.Time
并且 post 一天内有不同的时间,则上述方法无法正常工作。解决方法是检查呈现日期的变化(例如 $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 所示),它适用于任意切片类型(与另一个答案中的两种解决方案不同)。
我正在尝试使用在 Go 模板范围循环外声明的变量来查看前一个 post 是否与当前 post 发生在同一天。这是一个简化的例子。
其中 .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 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}}
标记之前的范围内。因此,当循环的下一次迭代到来时,您只会看到未更改的 "outer" 变量(因为您创建了一个新变量)。
您不能更改您创建的模板变量的值。
例如,您可以使用以下 range
形式:
{{ range $index, $post := .Posts }}
还有...
解决方案 #1:使用已注册的函数
并且您可以为模板注册一个函数(参见 template.Funcs()
),您可以将 $index
传递给该函数,它将 return 前一个元素的日期字段(位于$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 模板不支持复杂的逻辑。为此,有 Go 编程语言。由于这种理念,模板具有局限性。一项限制是无法更改模板变量。
处理此限制的一种方法是在 Go 中构建数据以匹配输出结构。创建一个类型来保存日期的 posts 并呈现这些类型的一部分。该模板仅包含 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 }}
如果 post Date
字段是 time.Time
并且 post 一天内有不同的时间,则上述方法无法正常工作。解决方法是检查呈现日期的变化(例如 $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 所示),它适用于任意切片类型(与另一个答案中的两种解决方案不同)。