【问题标题】:Can I access top template variables from nested template?我可以从嵌套模板访问顶级模板变量吗?
【发布时间】:2026-01-14 17:05:02
【问题描述】:

假设我有一个像这样嵌套子模板的模板。 playground link

package main

import (
    "os"
    "text/template"
)

type Person struct {
    FirstName  string
    SecondName string
}

type Document struct {
    DocName string
    People  []Person
}

const document = `
Document name: {{.DocName}}

{{range $person:=.People}}
{{template "person" $person}}
{{end}}

{{- define "person"}}
Person name is: {{.FirstName}} {{.SecondName}}
{{end}}
`

func main() {

    d := Document{
        DocName: "first try",
        People: []Person{
            {"Brian", "Kernighan"},
            {"Dennis", "Ritchie"},
        },
    }

    t := template.Must(template.New("document").Parse(document))

    err := t.Execute(os.Stdout, d)
    if err != nil {
        panic(err)
    }

}

一切正常,但现在我想设置一些文档范围的变量来改变所有模板及其子模板中的行为。像这样(不工作,恐慌)。 playground link

type Person struct {
    FirstName  string
    SecondName string
}

type Document struct {
    DocName string
    People  []Person

    SwitchNameOrder bool
}

const document = `
Document name: {{.DocName}}

{{range $person:=.People}}
{{template "person" $person}}
{{end}}

{{- define "person"}}
{{if $.SwitchNameOrder}} // <---- panic here
Person name is: {{.SecondName}} {{.FirstName}}
{{else}}
Person name is: {{.FirstName}} {{.SecondName}}
{{end}}
{{end}}
`

怎么做?有可能吗?

【问题讨论】:

    标签: go go-templates


    【解决方案1】:

    您可以做的一件事是使用模板函数将传入子模板的变量与来自父模板的变量“合并”。

    type Person struct {
        FirstName  string
        SecondName string
    }
    
    type Document struct {
        DocName string
        People  []Person
    
        SwitchNameOrder bool
    }
    
    func personWithDocument(p Person, d Document) interface{} {
        return struct {
            Person
            Document Document
        }{p, d}
    }
    
    t := template.Must(template.New("document").Funcs(template.FuncMap{
        "personWithDocument": personWithDocument,
    }).Parse(document))
    

    然后在模板中你会这样做:

    const document = `
    Document name: {{.DocName}}
    
    {{range $person:=.People}}
    {{template "person" (personWithDocument $person $) }}
    {{end}}
    
    {{- define "person"}}
    {{if .Document.SwitchNameOrder}}
    Person name is: {{.SecondName}} {{.FirstName}}
    {{else}}
    Person name is: {{.FirstName}} {{.SecondName}}
    {{end}}
    {{end}}
    `
    

    https://play.golang.org/p/YorPsMdr9g_H

    【讨论】:

    • 我发现了如何通过看起来不像的模板函数更有效地完成所有这些工作 ?- 见下文 - *.com/a/60635168/101152
    【解决方案2】:

    上述复杂解决方案的更好解决方案是停止尝试使用*配置选项,而是将其编写为模板函数,配置变量位于函数闭包中

    {{- define "person"}}
    {{if SwitchNameOrder}}
    Person name is: {{.SecondName}} {{.FirstName}}
    {{else}}
    Person name is: {{.FirstName}} {{.SecondName}}
    {{end}}
    {{end}}
    

    t := template.Must(template.New("document").Funcs(template.FuncMap{
        "SwitchNameOrder": func() bool {
            return switchNames // variable sits in closure
        },
    }).Parse(document))
    

    https://play.golang.org/p/O6QHtmxweOi

    另一种选择是将整个切换写成字符串函数,即:

    {{- define "person"}}
    Person name is: {{SwitchNames .FirstName .SecondName}}
    {{end}}
    

    SwitchNames 作为字符串函数

    ...Funcs(template.FuncMap{
        "SwitchNames": func(first, second string) string {
            if switchNames {
                return second + " " + first
            }
            return first + " " + second
        },
    })...
    

    根据实际的复杂性,可以更简洁也可以更简洁

    https://play.golang.org/p/UPB3NIpzw0N

    【讨论】:

      【解决方案3】:

      我最终做的是添加一个单独的结构配置并将其复制到任何地方。也就是说,

      type Config struct {
          SwitchNameOrder bool
      }
      
      type Person struct {
          FirstName  string
          SecondName string
      
          Config Config 
          // this could also be a pointer,
          // but I don't want to deal with nils, so let's copy
      }
      
      type Document struct {
          DocName string
          People  []Person
      
          Config Config
      }
      

      c := Config{SwitchNameOrder: true}
      
      d.Config = c
      for _, p := range d.People {
          p.Config = c
      }
      

      然后在模板中使用它

      
      {{- define "person"}}
      {{if .Config.SwitchNameOrder}}
      Person name is: {{.SecondName}} {{.FirstName}}
      {{else}}
      Person name is: {{.FirstName}} {{.SecondName}}
      {{end}}
      {{end}}
      

      很丑,但是怎么办

      https://play.golang.org/p/f95i4me8XLP

      【讨论】: