6

Go テンプレートの範囲ループの外で宣言された変数を使用して、前の投稿が現在の投稿と同じ日に発生したかどうかを確認しようとしています。簡単な例を次に示します。

where.Postsはそれぞれ a.Contentと aを持つ post 構造体の配列です.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 は、代入によるテンプレート変数の変更をサポートします。これは有効なコードになります:

{{ $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と、前の要素 ( ) の日付フィールドが返され$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: Post メソッドを使用する

このソリューションはアナログですが、さらに単純です。メソッドをに追加する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 プログラミング言語があります。この哲学の結果として、テンプレートには制限があります。制限の 1 つは、テンプレート変数を変更できないことです。

この制限に対処する 1 つの方法は、出力の構造に一致するように 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で、投稿が 1 日の中で異なる時間である場合、上記は期待どおりに機能しません。これを回避するには、レンダリングされた日付の変更を確認します (例: $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 }}

これは、値がテンプレート パッケージによって true と見なされることが保証されている場合にのみ機能します。

このソリューションでは、使用するたびにテンプレートを解析する必要はなく (他の回答のソリューション #1 のように)、任意のスライス タイプに適用されます (他の回答の両方のソリューションとは異なります)。

于 2017-12-18T01:38:34.530 に答える