043-模板渲染

数据渲染在我们平时编程中几乎每天都会用到。

可能有些同学不太明白数据渲染是含义,说白了就是在你的计算机屏幕上,将数据通过某种方式展现出来。最简单的例子就是使用 fmt.Printf 函数,将数据输出到屏幕了。

1. 使用 Printf 渲染数据

先来看一个最简单的例子。这里继续使用前面几篇用到的结构体 Movie.

type Movie struct {
    Title    string   `json:"title"`
    Year     int      `json:"release"`
    Color    bool     `json:"color"`
    Actors   []string `json:"actors"`
    duration int      `json:"duration"`
}
  • 例 1

定义一个对象 m:

m := Movie{
    Title: "Casablanca",
    Year:  1942,
    Color: false,
    Actors: []string{
        "Humphrey Bogart",
        "Ingrid Bergman",
    },
    duration: 1000,
}

使用 Printf 输出这个对象:

fmt.Printf("%s release in %d.\n", m.Title, m.Year)

最后会输出:

Casablanca release in 1942.

我相信对这种数据渲染方式你习以为常了。接下来,我们稍稍加点料。

  • 例 2

定义一个数组,输出所有电影发行时间,格式和例 1 一样。

movies := []Movie{
    {"Casablanca", 1942, false, []string{"Humphrey Bogart", "Ingrid Bergman"}, 1000},
    {"Cool Hand Luke", 1967, true, []string{"Paul Newman"}, 1258},
    {"Bullitt", 1968, true, []string{"Steve McQueen", "Jacqueline Bisset"}, 2389},
}

这对你来说太 easy 了。你可能会这样写:

for _, m := range movies {
    fmt.Printf("%s release in %d.\n", m.Title, m.Year)
}

当然了,这个例子还不够复杂,但是足以说明问题。我们可以认为 Printf 的第一个参数就是一种 template,"%s release in %d.\n",它就是一个模板。

如果你只看 "%s release in %d.\n",而不去看 Printf 后面的参数,也许你根本不知道这里的 %s%d 表示的是什么。另一方面,如果变量太多,你的 Printf 后面参数也会很多,很难适应复杂的需求,就比如说,如果我不仅要输出这个电影 release 的年限,还要输出所有的演员名字,你可能会写成下面这样:

for _, m := range movies {
    fmt.Printf("%s release in %d. Authors: %s\n",
        m.Title, m.Year, strings.Join(m.Actors, ","))
}

当然,上面的例子还能更复杂一些。为了能适应这种复杂的场景,golang 提供了更加牛逼的数据渲染方法,参考第 2 节。

2. text template

2.1 简介

毫不意外,我们仍然以一个例子开始。

m := Movie{
    Title: "Casablanca",
    Year:  1942,
    Color: false,
    Actors: []string{
        "Humphrey Bogart",
        "Ingrid Bergman",
    },
    duration: 1000,
}

这里继续使用第 1 节中的例 1,因为它足够简单。接下来,我们需要展示一段神奇的代码。

// 为了清晰展示,已经删除了部分不影响理解的代码

import "text/template"

// type Movie struct ...

func main() {
    // m := ...
    tmpl, _ := template.New("text").Parse("{{.Title}} release in {{.Year}}.\n")
    tmpl.Execute(os.Stdout, m)
}

最后输出:

Casablanca release in 1942.

可能到现在你还是一头雾水:

  1. 为什么上面两行可以达到和 fmt.Printf("%s release in %d.\n", m.Title, m.Year) 一样的效果?
  2. 这看起来似乎比 Printf 更加复杂了,为啥要用它?

先回答第 1 个问题, template 是 golang 提供的渲染数据的工具,它比 Printf 更加灵活,只要你遵守 template 的语法规则,就能完成很复杂的渲染效果。

第 2 个问题,在这里例子里,使用 template 我只能说有点大材小用。但是简单的例子能让你理解 template 能干什么。

2.2 template 如何工作

先来看看上面的 template 是如何将数据渲染出来的。

"{{.Title}} release in {{.Year}}.\n"

这是一个非常简单的 template,{{}} 在 template 表示的一个 Action,即『动作』,你可以简单的理解为当数据进行渲染的时候,会执行 {{}} 中的 Action(动作). 在 template 中,有一个 current cursor 的概念,即『当前游标』,在 template 使用 . (dot) 来表示。比如 {{.Title}} 表示当前对象里的 Title 成员。当模板执行渲染时,将 Title 成员的值渲染到这个位置。

current cursor 总是指向一个某个对象,并且随着数据的渲染,current cursor 可能会不断变化,等会在 2.3 节中你会遇到。第一个 current cursor 指向的对象就是 Execute 函数的第二个参数。

再看 template.New 函数,它的原型是:

func (t *Template) New(name string) *Template

它返回一个 Template 对象的指针,接下来调用这个对象的 Parse 函数,它的原型如下:

func (t *Template) Parse(text string) (*Template, error)

Parse 函数会继续返回一个 Template 指针,因此可以做到链式调用。Parse 阶段完成后,最后一步就是渲染阶段,即执行 Execute 函数。在上面的例子中,是将对象 m 渲染出的数据,定向到标准输出:

tmpl.Execute(os.Stdout, m)

tips: 为什么这个过程如此复杂呢?我们不妨这样理解,template 你可以理解成是一种全新的编译型编程语言,经过 Parse 函数编译后,再使用 Execute 函数去运行这个程序。这和正则表达式也非常像。

我知道这还不够过瘾,那我们使用 template 来完成第 1 节中的例 2 吧。

2.3 更加高级的 template

2.3.1 循环 action (一)

func main() {
    movies := []Movie{
        {"Casablanca", 1942, false, []string{"Humphrey Bogart", "Ingrid Bergman"}, 1000},
        {"Cool Hand Luke", 1967, true, []string{"Paul Newman"}, 1258},
        {"Bullitt", 1968, true, []string{"Steve McQueen", "Jacqueline Bisset"}, 2389},
    }

    tmpl, _ := template.New("text").
        Parse("{{range .}}{{.Title}} release in {{.Year}}.\n{{end}}")

    tmpl.Execute(os.Stdout, movies)
}

这个例子里,我们编写的模板是:"{{range .}}{{.Title}} release in {{.Year}}.\n{{end}}",这看起来比 2.2 中的更加复杂了,不过仍然好理解:

  • {{range .}} ... {{end}} 是 template 语法中的遍历结构,它们成对出现,在这里表示遍历 current cursor,此时 current cursor 指向的是 Execute 函数的第二个参数,即你传入的 movies.
  • {{.Title}} release in {{.Year}}.\n 位于 range action 的中间,range action 会修改 current cursor,在这里每一次循环,current cursor 都会修改为 movies 里的下一个元素。

2.2 中的 tips 说把 template 理解成为一种全新的编译型语言,到这里你应该有所体会了吧。如果我们把上面的 template 写成这样,你应该更容易理解:

{{range .}}
    {{.Title}} release in {{.Year}}.\n
{{end}}

2.3.2 循环 action (二)

接下来,我们再写一个 template,这一次我们需要输出所有的演员。

{{range .}}{{.Title}} release in {{.Year}}. Actors:{{range .Actors}}{{.}},{{end}}\n{{end}}

不过这样看起来不是很清晰,整理一下:

{{range .}}
    {{.Title}} release in {{.Year}}. Actors:
    {{range .Actors}}
        {{.}},
    {{end}}\n
{{end}}

当然你不能直接在代码里像上面这样写,最终你还是得写成一行。如果你写成多行,输出又是另一种模样了,你可以自己尝试一下不同排版之间输出有什么区别。

最后,我们的程序会输出:

Casablanca release in 1942. Actors:Humphrey Bogart,Ingrid Bergman,
Cool Hand Luke release in 1967. Actors:Paul Newman,
Bullitt release in 1968. Actors:Steve McQueen,Jacqueline Bisset,

3. 总结

这里介绍的模板只是 text/template 功能的冰山一角,具体不再展开。有时间,我会整理出一个专题专门讨论 template 语法。

在你学完 Go 基础语法前,这些东西都不要太过深入,否则只会越跑越偏,你只需要知道有这种东西即可。等你有一定功底后,再去慢慢研究和挖掘更多的功能吧。

  • 理解 template 基本用法

猜你喜欢

转载自blog.csdn.net/q1007729991/article/details/80374364