Go Web 編程之 模板(一)

概述

模板引擎是 Web 編程中必不可少的一個組件。模板能分離邏輯和數據,使得邏輯簡潔清晰,而且模板可複用。引用第二篇文章《程序結構》一文中的圖示,咱們能夠看到模板引擎在 Web 程序結構中的位置:html

模板引擎按照功能能夠劃分爲兩種類型:git

  • 無邏輯模板引擎:此類模板引擎只進行字符串的替換,無其它邏輯;
  • 嵌入邏輯模板引擎:此類模板引擎能夠在模板中嵌入邏輯,實現流程控制/循環等。

這兩類模板引擎都比較極端。無邏輯模板引擎須要在處理器中額外添加不少邏輯用於生成替換的文本。而嵌入邏輯模板引擎則在模板中混入了大量邏輯,致使維護性較差。實用的模板引擎通常介於這二者之間。github

在Go 語言中,text/templatehtml/template兩個庫實現模板功能。golang

模板內容能夠是 UTF-8 編碼的任何內容。其中用{{}}包圍的部分稱爲動做{{}}外的其它文本在輸出保持不變。模板須要應用到數據,模板中的動做會根據數據生成響應的內容來替換。web

模板解析以後能夠屢次執行,也能夠並行執行,可是注意使用同一個Writer會致使輸出交替出現。編程

模板的內容較多,我將分爲兩篇文章介紹。本文介紹text/template,包括 Go 模板的基本概念,用法和注意點。下篇文章介紹html/template數組

初體驗

使用模板引擎通常有 3 個步驟:bash

  • 定義模板(直接使用字符串字面量或文件);
  • 解析模板(使用text/templatehtml/template中的方法解析);
  • 傳入數據生成輸出。
package main

import (
	"log"
	"os"
	"text/template"
)

type User struct {
	Name string
	Age  int
}

func stringLiteralTemplate() {
	s := "My name is {{ .Name }}. I am {{ .Age }} years old.\n"
	t, err := template.New("test").Parse(s)
	if err != nil {
		log.Fatal("Parse string literal template error:", err)
	}

	u := User{Name: "darjun", Age: 28}
	err = t.Execute(os.Stdout, u)
	if err != nil {
		log.Fatal("Execute string literal template error:", err)
	}
}

func fileTemplate() {
	t, err := template.ParseFiles("test")
	if err != nil {
		log.Fatal("Parse file template error:", err)
	}

	u := User{Name: "dj", Age: 18}
	err = t.Execute(os.Stdout, u)
	if err != nil {
		log.Fatal("Execute file template error:", err)
	}
}

func main() {
	stringLiteralTemplate()

	fileTemplate()
}
複製代碼

在可執行程序目錄中新建模板文件test,並寫入下面的內容:編程語言

My name is {{ .Name }}. I am {{ .Age }} years old.

複製代碼

首先調用template.New建立一個模板,參數爲模板名。函數

而後調用Template類型的Parse方法,解析模板字符串,生成模板主體。這個方法返回兩個值。若是模板語法正確,則返回模板對象自己和一個 nil 值。 若是有語法錯誤,則返回一個 error 類型的值做爲第二個返回值,這時不該該使用第一個返回值。

最後,調用模板對象的Execute方法,傳入參數。Execute執行模板中的動做,將結果輸出到os.Stdout,即標準輸出。最終咱們看到模板中{{ .Name }}uName字段替換,{{ .Age }}uAge字段替換,標準輸出中顯示下面一行字符串:

My name is darjun. I am 28 years old.
複製代碼

上面代碼中,fileTemplate函數還演示瞭如何從文件中加載模板。其中template.ParseFiles方法會建立一個模板,並將用戶指定的模板文件名用做這個新模板的名字:

t, err := template.ParseFiles("test")
複製代碼

至關於:

t := template.New("test")
t, err := t.ParseFiles("test")
複製代碼

動做

Go 模板中的動做就是一些嵌入在模板裏面的命令。動做大致上能夠分爲如下幾種類型:

  • 點動做;
  • 條件動做;
  • 迭代動做;
  • 設置動做;
  • 包含動做。

點動做

在介紹其它的動做以前,咱們先看一個很重要的動做,點動做{{ . }})。它其實表明是傳遞給模板的數據,其餘動做或函數基本上都是對這個數據進行處理,以此來達到格式化和內容展現的目的。

對前面的代碼示例稍做修改:

func main() {
	s := "The user is {{ . }}."
	t, err := template.New("test").Parse(s)
	if err != nil {
		log.Fatal("Parse error:", err)
	}

	u := User{Name: "darjun", Age: 28}
	err = t.Execute(os.Stdout, u)
	if err != nil {
		log.Fatal("Execute error:", err)
	}
}
複製代碼

運行程序,標準輸出顯示:

The user is {darjun 28}.
複製代碼

實際上,{{ . }}會被替換爲傳給給模板的數據的字符串表示。這個字符串與以數據爲參數調用fmt.Sprint函數獲得的內容相同。咱們能夠爲User結構編寫一個方法:

func (u User) String() string {
	return fmt.Sprintf("(name:%s age:%d)", u.Name, u.Age)
}
複製代碼

這樣替換的字符串就是格式化以後的內容了:

The user is (name:darjun age:28).
複製代碼

注意:爲了使用的方便和靈活,在模板中不一樣的上下文內,.的含義可能會改變,下面在介紹不一樣的動做時會進行說明。

條件動做

在介紹動做的語法時,我採用 Go 標準庫中的寫法。我以爲這樣寫更嚴謹。 其中pipeline表示管道,後面會有詳細的介紹,如今能夠將它理解爲一個值。 T1/T2等形式表示語句塊,裏面能夠嵌套其它類型的動做。最簡單的語句塊就是不包含任何動做的字符串

條件動做的語法與編程語言中的if語句語法相似,有幾種形式:

形式一:

{{ if pipeline }} T1 {{ end }}
複製代碼

若是管道計算出來的值不爲空,執行T1。不然,不生成輸出。下面都表示空值:

  • false、0、空指針或接口;
  • 長度爲 0 的數組、切片、map或字符串。

形式二:

{{ if pipeline }} T1 {{ else }} T2 {{ end }}
複製代碼

若是管道計算出來的值不爲空,執行T1。不然,執行T2

形式三:

{{ if pipeline1 }} T1 {{ else if pipeline2 }} T2 {{ else }} T3 {{ end }}
複製代碼

若是管道pipeline1計算出來的值不爲空,則執行T1。反之若是管道pipeline2的值不爲空,執行T2。若是都爲空,執行T3

舉個栗子:

type AgeInfo struct {
	Age           int
	GreaterThan60 bool
	GreaterThan40 bool
}

func main() {
	t, err := template.ParseFiles("test")
	if err != nil {
		log.Fatal("Parse error:", err)
	}

	rand.Seed(time.Now().Unix())
	age := rand.Intn(100)
	info := AgeInfo {
		Age:           age,
		GreaterThan60: age > 60,
		GreaterThan40: age > 40,
	}
	err = t.Execute(os.Stdout, info)
	if err != nil {
		log.Fatal("Execute error:", err)
	}
}
複製代碼

在可執行程序的目錄下新建模板文件test,鍵入下面的內容:

Your age is: {{ .Age }}
{{ if .GreaterThan60 }}
Old People!
{{ else if .GreaterThan40 }}
Middle Aged!
{{ else }}
Young!
{{ end }}
複製代碼

運行程序,會隨機一個年齡,而後根據年齡區間選擇性輸出Old People!/Middle Age!/Young!其中一個。下面是我運行兩次運行的輸出:

Your age is: 7

Young!

複製代碼
Your age is: 79

Old People!

複製代碼

這個程序有一個問題,會有多餘的空格!咱們以前說過,除了動做以外的任何文本都會原樣保持,包括空格和換行!針對這個問題,有兩種解決方案。第一種方案是刪除多餘的空格和換行,test文件修改成:

Your age is: {{ .Age }}
{{ if .GreaterThan60 }}Old People!{{ else if .GreaterThan40 }}Middle Aged!{{ else }}Young!{{ end }}
複製代碼

顯然,這個方法會致使模板內容很難閱讀,不夠理想。爲此,Go 提供了針對空白符的處理。若是一個動做以{{-(注意有一個空格),那麼該動做與它前面相鄰的非空文本或動做間的空白符將會被所有刪除。相似地,若是一個動做以-}}結尾,那麼該動做與它後面相鄰的非空文本或動做間的空白符將會被所有刪除。例如:

{{23 -}} < {{- 45}}
複製代碼

將會生成輸出:

23<45
複製代碼

回到咱們的例子中,咱們能夠將test文件稍做修改:

Your age is: {{ .Age }}
{{ if .GreaterThan60 -}}
"Old People!"
{{- else if .GreaterThan40 -}}
"Middle Aged!"
{{- else -}}
"Young!"
{{- end }}
複製代碼

這樣,輸出的文本就不會包含多餘的空格了。

迭代動做

迭代其實與編程語言中的循環遍歷相似。有兩種形式:

形式一:

{{ range pipeline }} T1 {{ end }}
複製代碼

管道的值類型必須是數組、切片、map、channel。若是值的長度爲 0,那麼無輸出。不然,.被設置爲當前遍歷到的元素,而後執行T1,即在T1.表示遍歷的當前元素,而非傳給模板的參數。若是值是 map 類型,且鍵是可比較的基本類型,元素將會以鍵的順序訪問

形式二:

{{ range pipeline }} T1 {{ else }} T2 {{ end }}
複製代碼

與前一種形式基本同樣,若是值的長度爲 0,那麼執行T2

舉個栗子:

type Item struct {
	Name	string
	Price	int
}

func main() {
	t, err := template.ParseFiles("test")
	if err != nil {
		log.Fatal("Parse error:", err)
	}

	items := []Item {
		{ "iPhone", 5499 },
		{ "iPad", 6331 },
		{ "iWatch", 1499 },
		{ "MacBook", 8250 },
	}

	err = t.Execute(os.Stdout, items)
	if err != nil {
		log.Fatal("Execute error:", err)
	}
}
複製代碼

在可執行程序目錄下新建模板文件test,鍵入內容:

Apple Products:
{{ range . }}
{{ .Name }}: ¥{{ .Price }}
{{ else }}
No Products!!!
{{ end }}
複製代碼

運行程序,獲得下面的輸出:

Apple Products:

iPhone: ¥5499

iPad: ¥6331

iWatch: ¥1499

MacBook: ¥8250

複製代碼

range語句循環體內,.被設置爲當前遍歷的元素,能夠直接使用{{ .Name }}{{ .Price }}訪問產品名稱和價格。在程序中,將nil傳給Execute方法會獲得下面的輸出:

Apple Products:

No Products!!!

複製代碼

設置動做

設置動做使用with關鍵字重定義.。在with語句內,.會被定義爲指定的值。通常用在結構嵌套很深時,能起到簡化代碼的做用。

形式一:

{{ with pipeline }} T1 {{ end }}
複製代碼

若是管道值不爲空,則將.設置爲pipeline的值,而後執行T1。不然,不生成輸出。

形式二:

{{ with pipeline }} T1 {{ else }} T2 {{ end }}
複製代碼

與前一種形式的不一樣之處在於當管道值爲空時,不改變.執行T2。舉個栗子:

type User struct {
	Name string
	Age  int
}

type Pet struct {
	Name  string
	Age   int
	Owner User
}

func main() {
	t, err := template.ParseFiles("test")
	if err != nil {
		log.Fatal("Parse error:", err)
	}

	p := Pet {
		Name:  "Orange",
		Age:   2,
		Owner: User {
			Name: "dj",
			Age:  28,
		},
	}

	err = t.Execute(os.Stdout, p)
	if err != nil {
		log.Fatal("Execute error:", err)
	}
}
複製代碼

模板文件內容:

Pet Info:
Name: {{ .Name }}
Age: {{ .Age }}
Owner:
{{ with .Owner }}
  Name: {{ .Name }}
  Age: {{ .Age }}
{{ end }}
複製代碼

運行程序,獲得下面的輸出:

Pet Info:
Name: Orange
Age: 2
Owner:

  Name: dj
  Age: 28

複製代碼

可見,在with語句內,.被替換成了Owner字段的值。

包含動做

包含動做能夠在一個模板中嵌入另外一個模板,方便模板的複用。

形式一:

{{ template "name" }}
複製代碼

形式二:

{{ template "name" pipeline }}
複製代碼

其中name表示嵌入的模板名稱。第一種形式,將使用nil做爲傳入內嵌模板的參數。第二種形式,管道pipeline的值將會做爲參數傳給內嵌的模板。舉個栗子:

package main

import (
	"log"
	"os"
	"text/template"
)

func main() {
	t, err := template.ParseFiles("test1", "test2")
	if err != nil {
		log.Fatal("Parse error:", err)
	}

	err = t.Execute(os.Stdout, "test data")
	if err != nil {
		log.Fatal("Execute error:", err)
	}
}
複製代碼

ParseFiles方法接收可變參數,可將任意多個文件名傳給該方法。

模板test1:

This is in test1.
{{ template "test2" }}

{{ template "test2" . }}
複製代碼

模板test2:

This is in test2.
Get: {{ . }}.
複製代碼

運行程序獲得輸出:

This is in test1.
This is in test2.
Get: <no value>.

This is in test2.
Get: test data.
複製代碼

前一個嵌入模板,沒有傳遞參數。後一個傳入.,即傳給test1模板的參數。

其它元素

在介紹了幾種動做以後,咱們回過頭來看幾種基本組成部分。

註釋

註釋只有一種語法:

{{ /* 註釋 */ }}
複製代碼

註釋的內容不會呈如今輸出中,它就像代碼註釋同樣,是爲了讓模板更易讀。

參數

一個參數就是模板中的一個值。它的取值有多種:

  • 布爾值、字符串、字符、整數、浮點數、虛數和複數等字面量
  • 結構中的一個字段或 map 中的一個鍵。結構的字段名必須是導出的,即大寫字母開頭,map 的鍵名則沒必要
  • 一個函數或方法。必須只返回一個值,或者只返回一個值和一個錯誤。若是返回了非空的錯誤,則Execute方法執行終止,返回該錯誤給調用者;
  • 等等等等。

上面幾種形式能夠結合使用:

{{ .Field1.Key1.Method1.Field2.Key2.Method2 }
複製代碼

其實,咱們已經用過不少次參數了。下面看一個方法調用的栗子:

type User struct {
	FirstName 	string
	LastName	string
}

func (u User) FullName() string {
	return u.FirstName + " " + u.LastName
}

func main() {
	t, err := template.ParseFiles("test")
	if err != nil {
		log.Fatal("Parse error:", err)
	}

	err = t.Execute(os.Stdout, User{FirstName: "lee", LastName: "darjun"})
	if err != nil {
		log.Fatal("Execute error:", err)
	}
}
複製代碼

模板文件test

My full name is {{ .FullName }}.
複製代碼

模板執行會使用FullName方法的返回值替換{{ .FullName }},輸出:

My full name is lee darjun.
複製代碼

關於參數的幾個要點:

  • 參數能夠是任何類型;
  • 若是參數爲指針,實現會根據須要取其基礎類型;
  • 若是參數計算獲得一個函數類型,它不會自動調用。例如{{ .Method1 }},若是Method1方法返回一個函數,那麼返回值函數不會調用。若是要調用它,使用內置的call函數。

管道

管道的語法與 Linux 中的管道相似,即命令的鏈式序列:

{{ p1 | p2 | p3 }}
複製代碼

每一個單獨的命令(即p1/p2/p3...)能夠是下面三種類型:

  • 參數,見上面;
  • 可能帶有參數的方法調用;
  • 可能帶有參數的函數調用。

在一個鏈式管道中,每一個命令的結果會做爲下一個命令的最後一個參數。最後一個命令的結果做爲整個管道的值。

管道必須只返回一個值,或者只返回一個值和一個錯誤。若是返回了非空的錯誤,那麼Execute方法執行終止,並將該錯誤返回給調用者。

在迭代程序的基礎上稍做修改:

type Item struct {
	Name  string
	Price float64
	Num   int
}

func (item Item) Total() float64 {
	return item.Price * float64(item.Num)
}

func main() {
	t, err := template.ParseFiles("test")
	if err != nil {
		log.Fatal("Parse error:", err)
	}

	item := Item {"iPhone", 5499.99, 2 }

	err = t.Execute(os.Stdout, item)
	if err != nil {
		log.Fatal("Execute error:", err)
	}
}
複製代碼

模板文件test

Product: {{ .Name }}
Price: ¥{{ .Price }}
Num: {{ .Num }}
Total: ¥{{ .Total | printf "%.2f" }}
複製代碼

先調用Item.Total方法計算商品總價,而後使用printf格式化,保留兩位小數。最終輸出:

Product: iPhone
Price: ¥5499.99
Num: 2
Total: ¥10999.98
複製代碼

printf是 Go 模板內置的函數,這樣的函數還有不少。

變量

在動做中,能夠用管道的值定義一個變量。

$variable := pipeline
複製代碼

$variable爲變量名,聲明變量的動做不生成輸出。

相似地,變量也能夠從新賦值:

$variable = pipeline
複製代碼

range動做中能夠定義兩個變量:

range $index, $element := range pipeline
複製代碼

這樣就能夠在循環中經過$index$element訪問索引和元素了。

變量的做用域持續到定義它的控制結構的{{ end }}動做。若是沒有這樣的控制結構,則持續到模板結束。模板調用不繼承變量。

執行開始時,$被設置爲傳入的數據參數,即.的值。

函數

Go 模板提供了大量的預約義函數,若是有特殊需求也能夠實現自定義函數。模板執行時,遇到函數調用,先從模板自定義函數表中查找,然後查找全局函數表。

預約義函數

預約義函數分爲如下幾類:

  • 邏輯運算,and/or/not
  • 調用操做,call
  • 格式化操做,print/printf/println,與用參數直接調用fmt.Sprint/Sprintf/Sprintln獲得的內容相同;
  • 比較運算,eq/ne/lt/le/gt/ge

在上面條件動做的示例代碼中,咱們在代碼中計算出大小關係再傳入模板,這樣比較繁瑣,能夠直接使用比較運算簡化。

有兩點須要注意:

  • 因爲是函數調用,全部的參數都會被求值,沒有短路求值
  • 比較運算只做用於基本類型,且沒有 Go 語法那麼嚴格,例如能夠比較有符號和無符號整數。

自定義函數

默認狀況下,模板中無自定義函數,可使用模板的Funcs方法添加。下面咱們實現一個格式化日期的自定義函數:

package main

import (
	"log"
	"os"
	"text/template"
	"time"
)

func formatDate(t time.Time) string {
	return t.Format("2016-01-02")
}

func main() {
	funcMap := template.FuncMap {
		"fdate": formatDate,
	}
	t := template.New("test").Funcs(funcMap)
	t, err := t.ParseFiles("test")
	if err != nil {
		log.Fatal("Parse errr:", err)
	}

	err = t.Execute(os.Stdout, time.Now())
	if err != nil {
		log.Fatal("Exeute error:", err)
	}
}
複製代碼

模板文件test

Today is {{ . | fdate }}.
複製代碼

模板的Func方法接受一個template.FuncMap類型變量,鍵爲函數名,值爲實際定義的函數。 能夠一次設置多個自定義函數。自定義函數要求只返回一個值,或者返回一個值和一個錯誤。 設置以後就能夠在模板中使用fdate了,輸出:

Today is 7016-01-07.
複製代碼

這裏不能使用template.ParseFiles,由於在解析模板文件的時候fdate未定義會致使解析失敗。必須先建立模板,調用Funcs設置自定義函數,而後再解析模板。

模板的幾種建立方式

咱們前面學習了兩種模板的建立方式:

  • 先調用template.New建立模板,而後使用Parse/ParseFiles解析模板內容;
  • 直接使用template.ParseFiles建立並解析模板文件。

第一種方式,調用template.New建立模板時須要傳入一個模板名字,後續調用ParseFiles能夠傳入一個或多個文件,這些文件中必須有一個基礎名(即去掉路徑部分)與模板名相同。若是沒有文件名與模板名相同,則Execute調用失敗,返回錯誤。例如:

package main

import (
	"log"
	"os"
	"text/template"
)

func main() {
	t := template.New("test")
	t, err := t.ParseFiles("test1")

	if err != nil {
		log.Fatal("Parse error:", err)
	}

	err = t.Execute(os.Stdout, nil)
	if err != nil {
		log.Fatal("Execute error:", err)
	}
}
複製代碼

上面代碼先建立模板test,而後解析文件test1。執行該程序會出現下面的錯誤:

Execute error:template: test: "test" is an incomplete or empty template
複製代碼

Why?

咱們先來看看模板的結構:

// src/text/template.go
type common struct {
	tmpl   map[string]*Template // Map from name to defined templates.
	option option
	muFuncs    sync.RWMutex // protects parseFuncs and execFuncs
	parseFuncs FuncMap
	execFuncs  map[string]reflect.Value
}

type Template struct {
	name string
	*parse.Tree
	*common
	leftDelim  string
	rightDelim string
}
複製代碼

模板結構Template中有一個字段commoncommon中又有一個字段tmpl保存名字到模板的映射。其實,最外層的Template結構是主模板,咱們調用Execute方法時執行的就是主模板。 執行ParseFiles方法時,每一個文件都會生成一個模板。只有文件基礎名與模板名相同時,該文件的內容纔會解析到主模板中。這也是上面的程序執行失敗的緣由——主模板爲空。 其它文件解析生成關聯模板,存儲在字段tmpl中。關聯模板能夠是在主模板中經過{{ define }}動做定義,或者在非主模板文件中定義。關聯模板也能夠執行,可是須要使用ExecuteTemplate方法,顯式傳入模板名:

func main()
    t := template.New("test")
    t, err := t.ParseFiles("test1")
    
    if err != nil {
    	log.Fatal("in associatedTemplate Parse error:", err)
    }
    
    err = t.ExecuteTemplate(os.Stdout, "test1", nil)
    if err != nil {
    	log.Fatal("in associatedTemplate Execute error:", err)
    }
}
複製代碼

第二種方式將建立和解析兩步合併在一塊兒了。template.ParseFiles方法將傳入的第一個文件名做爲模板名稱,其他的文件(若是有的話)解析後存放在tmpl中。

t, err := template.ParseFiles("file1", "file2", "file3")
複製代碼

其實就等價於:

t := template.New("file1")
t, err := t.ParseFiles("file1", "file2", "file3")
複製代碼

少了不一致的可能性,因此調用Execute方法時不會出現上面的錯誤。

還有一種建立方式,使用ParseGlob函數。ParseGlob會對匹配給定模式的全部文件進行語法分析。

func main() {
    t, err := template.ParseGlob("tmpl*.glob")
	if err != nil {
		log.Fatal("in globTemplate parse error:", err)
	}

	err = t.Execute(os.Stdout, nil)
	if err != nil {
		log.Fatal(err)
	}

	for i := 1; i <= 3; i++ {
		err = t.ExecuteTemplate(os.Stdout, fmt.Sprintf("tmpl%d.glob", i), nil)
		if err != nil {
			log.Fatal(err)
		}
	}
}
複製代碼

ParseGlob返回的模板以匹配的第一個文件基礎名做爲名稱。ParseGlob解析時會對同一個目錄下的文件進行排序,因此第一個文件老是固定的。

咱們建立三個模板文件,tmpl1.glob

In glob template file1.

複製代碼

tmpl2.glob

In glob template file2.

複製代碼

tmpl3.glob

In glob template file3.

複製代碼

最終輸出爲:

In glob template file1.
In glob template file1.
In glob template file2.
In glob template file3.

複製代碼

注意,若是多個不一樣路徑下的文件名相同,那麼後解析的會覆蓋以前的。

嵌套模板

在一個模板文件中還能夠經過{{ define }}動做定義其它的模板,這些模板就是嵌套模板。模板定義必須在模板內容的最頂層,像 Go 程序中的全局變量同樣。

嵌套模板通常用於佈局(layout)。不少文本的結構其實很是固定,例如郵件有標題和正文,網頁有首部、正文和尾部等。 咱們能夠爲這些固定結構的每部分定義一個模板。

定義模板文件layout.tmpl

{{ define "layout" }}
This is body.
{{ template "content" . }}
{{ end }}

{{ define "content" }}
This is {{ . }} content.
{{ end }}
複製代碼

上面定義了兩個模板layoutcontentlayout中使用了content。執行這種方式定義的模板必須使用ExecuteTemplate方法:

func main() {
	t, err := template.ParseFiles("layout.tmpl")
	if err != nil {
		log.Fatal("Parse error:", err)
	}

	err = t.ExecuteTemplate(os.Stdout, "layout", "amazing")
	if err != nil {
		log.Fatal("Execute error:", err)
	}
}
複製代碼

嵌套模板在網頁佈局中應用很是普遍,下一篇文章介紹html/template時還會講到。

塊動做

塊動做其實就是定義一個默認模板,語法以下:

{{ block "name" arg }}
T1
{{ end }}
複製代碼

其實它就等價於定義一個模板,而後當即使用它:

{{ define "name" }}
T1
{{ end }}

{{ template "name" arg }}
複製代碼

若是後面定義了模板content,那麼使用後面的定義,不然使用默認模板。

例如上面的示例中,咱們將模板修改以下:

{{ define "layout" }}
This is body.
{{ block "content" . }}
This is default content.
{{ end }}
{{ end }}
複製代碼

去掉後面的content模板定義,執行layout時,content部分會顯示默認值。

總結

本文介紹了 Go 提供的模板text/template。模板比較簡單易用,對於一些細節須要多加留意。代碼在Github上。

參考連接

  1. Go Web 編程
  2. text/template文檔
相關文章
相關標籤/搜索