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),它适用于任意切片类型(与另一个答案中的两个解决方案不同)。