golang html/template 如何创建一个全局变量并在多个地方改变?

How to create a global variable and change in multiple places in golang html/template?

我正在 html/template 中创建一个变量,并根据条件更改值。但是值的范围只停留在 if 条件内:

{{if .UserData}}
    {{$currentUserId := .UserData.UserId}}
    [<a href="#ask_question">Inside {{$currentUserId}}</a>]
{{else}}
    {{$currentUserId := 0}}
{{end}}
[<a href="#ask_question">outside {{$currentUserId}}</a>]

在 if 条件内我得到了正确的值,但在它之外是 0。如何在条件之外使用 $currentUserId?有人可以帮我解决这个问题吗?

Go 1.11 添加了对 changing values of template variables 的支持。要定义变量,请使用 :=:

{{$currentUserId := 0}}

要更改其值,请使用赋值 =:

{{$currentUserId = .UserData.UserId}}

如果变量在 {{if}} 块之外创建但在其中更改,则更改将在 {{if}} 块之后可见。

{{$currentUserId := 0 -}}
Before: {{$currentUserId}}
{{if .UserData -}}
    {{$currentUserId = .UserData.UserId}}
    [<a href="#ask_question">Inside {{$currentUserId}}</a>]
{{else}}
    {{$currentUserId = 0}}
{{end}}
[<a href="#ask_question">outside {{$currentUserId}}</a>]

像这样测试:

m := map[string]interface{}{}
t := template.Must(template.New("").Parse(src))

m["UserData"] = UserData{99}
if err := t.Execute(os.Stdout, m); err != nil {
    panic(err)

输出是(在Go Playground上试试):

Before: 0

    [<a href="#ask_question">Inside 99</a>]

[<a href="#ask_question">outside 99</a>]

原回答如下。


简短的回答是:你不能。

根据设计理念,模板不应包含复杂的逻辑。在模板中,您只能创建新变量,不能更改现有模板变量的值。当您在 {{if}} 块内执行 {{$currentUserId := .UserData.UserId}} 时,会创建一个新变量来覆盖外部变量,并且其范围扩展到 {{end}} 操作。

text/template package, Variables section (the same applies to the html/template 包中也有描述):

A variable's scope extends to the "end" action of the control structure ("if", "with", or "range") in which it is declared, or to the end of the template if there is no such control structure. A template invocation does not inherit variables from the point of its invocation.

可能的解决方法

在您的情况下,最简单的方法是将自定义函数 CurrentUserId() 注册到您的模板,如果 UserData 存在,returns 它的 ID UserData.UserId(),如果它不存在,returns 0 就像你的情况一样。

这是如何完成的示例:

type UserData struct {
    UserId int
}

func main() {
    m := map[string]interface{}{}
    t := template.Must(template.New("").Funcs(template.FuncMap{
        "CurrentUserId": func() int {
            if u, ok := m["UserData"]; ok {
                return u.(UserData).UserId
            }
            return 0
        },
    }).Parse(src))

    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
    m["UserData"] = UserData{99}
    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
}

const src = `Current user id: {{CurrentUserId}}
`

它首先执行不带 UserData 的模板,然后执行带有 UserDataUserId = 99 的模板。输出是:

Current user id: 0
Current user id: 99

Go Playground 上试试。

模拟可变变量

您还可以模拟可变变量,但也可以使用已注册的自定义函数。你可以注册一个像 SetCurrentUserId() 这样的函数,它会改变一个变量的值,最好是在传递给模板执行的参数中,这样它对于并发使用来说仍然是安全的。

这是一个如何操作的示例(它使用 map 作为模板数据,并且 SetCurrentUserId() 将当前用户 ID 设置为该映射中的一个值):

func main() {
    m := map[string]interface{}{}
    t := template.Must(template.New("").Funcs(template.FuncMap{
        "SetCurrentUserId": func(id int) string {
            m["CurrentUserId"] = id
            return ""
        },
    }).Parse(src))

    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
    m["UserData"] = UserData{99}
    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
}

const src = `Before: {{.CurrentUserId}}
{{if .UserData}}
    {{SetCurrentUserId .UserData.UserId}}Inside: {{.CurrentUserId}}
{{else}}
    {{SetCurrentUserId 0}}Inside: {{.CurrentUserId}}
{{end}}
After: {{.CurrentUserId}}
`

这再次首先执行不带 UserData 的模板,然后执行带 UserData 且具有 UserId = 99 的模板。输出是:

Before: 
    Inside: 0
After: 0
Before: 0
    Inside: 99
After: 99

Go Playground 上试试。