什么是go - template
你可以把它理解为Java的jsp,理解为js的ejs,vue的template,等等就是一个模版,所以就是需要了解一些模版的语法,以及渲染机制等,本文基本覆盖的很全面,模版其实核心是理解:1、基本语法,2、自定义func使用和内置func使用,3、变量的使用,4、模版复用等机制。
如果你掌握以上我说的,那么开发一个模版工具,是很轻松的,比如orm通用代码,其他工具等。
以下就是个demo:
func TestTemplateString(t *testing.T) {
tmpl := "my name is {{.}}"
parse, _ := template.New("demo").Parse(tmpl)
_ = parse.Execute(os.Stdout, "anthony")
}
// my name is anthony
所以需要模板渲染的部分都需要加入{{}} , 有些部分操作需要加入{{end}} 作为标识符等。
简单语法学习{{.}}
可以展示任何数据,各种类型的数据,可以理解为接收类型是interface{}
func TestTemplateString(t *testing.T) {
tmpl := "my name is {{.}}"
parse, _ := template.New("demo").Parse(tmpl)
_ = parse.Execute(os.Stdout, []string{"hao", "dong"})
}
=== RUN TestTemplateString
my name is [hao dong]--- PASS: TestTemplateString (0.00s)
PASS
{{.Field}}
func TestTemplateStruct(t *testing.T) {
tmpl := `my name is "{{.Name}}"`
parse, _ := template.New("demo").Parse(tmpl)
_ = parse.Execute(os.Stdout, struct{ Name string }{Name: "anthony"})
}
{{/* a comment */}}
? 给go文件加入注释
func TestTemplateCommon(t *testing.T) {
tmpl := `{{/* a comment */}}`
parse, _ := template.New("demo").Parse(tmpl)
_ = parse.Execute(os.Stdout, struct{ Name string }{Name: "anthony"})
}
// 什么也不输出
{{-content-}}
去除前后空格,超级方便好用
func TestTemplateTrim(t *testing.T) {
tmpl := `
{{- . -}} `
parse, _ := template.New("demo").Parse(tmpl)
_ = parse.Execute(os.Stdout, "hello")
}
//=== RUN TestTemplateTrim
//hello--- PASS: TestTemplateTrim (0.00s)
如果不去会是这样子
func TestTemplateTrim(t *testing.T) {
tmpl := `
{{ . }} `
parse, _ := template.New("demo").Parse(tmpl)
_ = parse.Execute(os.Stdout, "hello")
}
//=== RUN TestTemplateTrim
//
// hello --- PASS: TestTemplateTrim (0.00s)
//PASS
条件语句{{ifcondition}}do{{else}}doelse{{end}}
这个语意是如果true,则do,否则do-else,必须最后申明{{end}} , 跟我的ifelse 一模一样的
doelse的条件为 : false、0、任意 nil 指针、接口值、数组、切片、字典和空字符串""(长度为 0 的字符串)。
func TestTemplateIf(t *testing.T) {
tmpl := `{{if .flag -}}
The flag=true
{{- else -}}
The flag=false
{{- end}}
`
parse, _ := template.New("demo").Parse(tmpl)
_ = parse.Execute(os.Stdout, _map{
"flag": true,
})
_ = parse.Execute(os.Stdout, _map{
"flag": false,
})
}
=== RUN TestTemplateIf
The flag=true
The flag=false
--- PASS: TestTemplateIf (0.00s)
PASS
循环语句{{range content}}T1{{end}}
? 语意很简单,就是遍历内容
func TestRange(t *testing.T) {
tmpl := `{{range .Array}}
{{- . -}},
{{- end}}
`
parse, _ := template.New("demo").Parse(tmpl)
_ = parse.Execute(os.Stdout, struct {
Array []string
}{Array: []string{"a", "b", "c"}})
}
//=== RUN TestRange
//a,b,c,
//--- PASS: TestRange (0.00s)
{{range content}}T1{{else}}T0{{end}}
? 如果数组为空,输出else的东西
func TestRangeEmpty(t *testing.T) {
tmpl := `{{range .Array}}
{{- . -}},
{{else -}}
array null
{{- end}}
`
parse, _ := template.New("demo").Parse(tmpl)
_ = parse.Execute(os.Stdout, struct {
Array []string
}{Array: []string{}})
}
//=== RUN TestRangeEmpty
//array null
//--- PASS: TestRangeEmpty (0.00s)
{{range $index,$value:=pipeline}}T1{{end}}
? {{range $key,$value :=pipeline }}T1 {{end}} 也适用,用于遍历
? 关于$key,这个属于变量操作,后面会讲到
func TestRange(t *testing.T) {
tmpl := `{{range $key, $value:=.Array}}
{{- $key}}:{{$value}},
{{- end}}
`
parse, _ := template.New("demo").Parse(tmpl)
_ = parse.Execute(os.Stdout, struct {
Array map[string]string
}{Array: map[string]string{"a": "1", "b": "2", "c": "3"}})
}
//=== RUN TestRange
//a:1,b:2,c:3,
//--- PASS: TestRange (0.00s)
{{ range pipeline }} T1 {{ end }}
// 这个 else 比较有意思,如果 pipeline 的长度为 0 则输出 else 中的内容
{{ range pipeline }} T1 {{ else }} T0 {{ end }}
// 获取容器的下标
{{ range $index, $value := pipeline }} T1 {{ end }}
FuncMap(很重要)1、自定义函数
type FuncMapmap[string]interface{}
? 它有一个比较好的功能就是PHP字符串变量,自定义函数
func TestFuncMap(t *testing.T) {
tem, _ := template.New("").Funcs(map[string]interface{}{
"ReplaceAll": func(src string, old, new string) string {
return strings.ReplaceAll(src, old, new)
},
}).Parse(`func replace: {{ReplaceAll .content "a" "A"}}`)
tem.Execute(os.Stdout, map[string]interface{}{
"content": "aBC",
})
}
=== RUN TestFuncMap
func replace: ABC--- PASS: TestFuncMap (0.00s)
PASS
其中,不能写{{ReplaceAll.content a A}} ,因为go不识别 a 是一个字符串,所以必须加引号
如果理解了这个,其实对于一些内置函数会理解很多,其中对于函数的要求是:
// 写法错误:不允许有两个参数返回值,如果是两个返回值,第二个必须是error
"ReplaceAll": func(src string, old, new string) (string, string) {
return strings.ReplaceAll(src, old, new), "111"
},
// 写法正确: 如果两个返回类型,第二个必须是error,顺序不能颠倒
"ReplaceAll": func(src string, old, new string) (string, error) {
return strings.ReplaceAll(src, old, new), nil
},
// 写法正确: 如果是一个返回类型,直接返回就行了
"ReplaceAll": func(src string, old, new string) string {
return strings.ReplaceAll(src, old, new)
},
// 写法正确:参数可以不传递,但是必须有返回值的(其实可以理解,没有返回值,你渲染啥)
tem, _ := template.New("").Funcs(map[string]interface{}{
"Echo": func() string {
return "hello world"
},
}).Parse(`func echo : {{Echo}}`)
// 写法错误:不允许没有返回参数,直接panic
"ReplaceAll": func(src string, old, new string) {
strings.ReplaceAll(src, old, new)
},
? 其实理解了funcmap,你就理解了内置函数如何玩的,接下来就会说的,不用死记硬背。
2、内置函数
内置函数本质上也是 FuncMap,所以如果掌握了如何使用FuncMap,其实就会这个了。
eq
func TestEQ(t *testing.T) {
parse, _ := template.New("").Parse(`{{eq .content1 .content2}}`)
parse.Execute(os.Stdout, _map{
"content1": "a",
"content2": "b",
})
}
=== RUN TestEQ
false--- PASS: TestEQ (0.00s)
PASS
call
func TestCall(t *testing.T) {
parse, _ := template.New("").Parse(`{{call .fun .param}}
`)
parse.Execute(os.Stdout, _map{
"fun": func(str string) string { return strings.ToUpper(str) },
"param": "abc",
})
}
=== RUN TestCall
ABC
--- PASS: TestCall (0.00s)
PASS
其实就是这么简单,对于call函数来说,它必须要求返回参数格式是1或2个,其中如果是两个则必须一个是error
func call(fn reflect.Value, args ...reflect.Value) (reflect.Value, error)
func safeCall(fun reflect.Value, args []reflect.Value) (val reflect.Value, err error) {
defer func() {
if r := recover(); r != nil {
if e, ok := r.(error); ok {
err = e
} else {
err = fmt.Errorf("%v", r)
}
}
}()
ret := fun.Call(args)
// 结果如果两个,则必须有一个是error
if len(ret) == 2 && !ret[1].IsNil() {
return ret[0], ret[1].Interface().(error)
}
return ret[0], nil
}
变量
? 变量通常适用于定义了某个值,申明很简单,和写php一样,变量前面需要加入一个$ ,注意的是需要渲染的部分全部需要用{{}} 包起来
func TestVariable(t *testing.T) {
tmpl, err := template.New("test").Parse(`{{$a := "anthony"}} hello {{$a}}`)
if err != nil {
panic(err)
}
err = tmpl.Execute(os.Stdout, nil)
if err != nil {
panic(err)
}
}
其实跟我的go预发基本一致。
模版嵌套
? 自定义内嵌的模板{{define "template_name"}}template_content {{end}},其中就是一种模版复用的机制。 那么如何模版引用呢{{template"template_name""args"}} ,记得传入两个参数就行,一个模版的名称(记住申明模版名称需要加上""),一个是你的模版需要的参数。
func TestTemplateInternal(t *testing.T) {
parse, _ := template.New("").Parse(`
{{define "print"}}my name is {{.}} {{end}}
{{- template "print" .name}}
`)
parse.Execute(os.Stdout, _map{
"name": "hao dong",
})
}
=== RUN TestTemplateInternal
my name is hao dong
--- PASS: TestTemplateInternal (0.00s)
PASS
format 工具
? 我们知道,go是有个fmt插件,可以帮助format 文件,所以在这里go 也提供了format
func TestFormat(t *testing.T) {
parse, _ := template.New("").Parse(`
package main
import "fmt"
func main(){
fmt.Println("{{.}}")
}
`)
parse.Execute(os.Stdout, _map{"data":"1111"})
}
// 结果:是不是很乱,但是别慌
=== RUN TestFormat
package main
import "fmt"
func main(){
fmt.Println("map[data:1111]")
}
--- PASS: TestFormat (0.00s)
PASS
如何使用format呢 ?
func TestFormat(t *testing.T) {
parse, _ := template.New("").Parse(`
package main
import "fmt"
func main(){
fmt.Println("{{.}}")
}
`)
buffer := &bytes.Buffer{}
parse.Execute(buffer, _map{"data":"1111"})
source, _ := format.Source(buffer.Bytes())
fmt.Printf("%s",source)
}
=== RUN TestFormat
package main
import "fmt"
func main() {
fmt.Println("map[data:1111]")
}
--- PASS: TestFormat (0.00s)
PASS
是不是变得很整齐。
总结
go 原生的提供了模版机制,对于开发者相当友好,尽管第三方包有很多模版工具,但是原生是最好的,因为不需要引入依赖。
模版通常用于生成一些复用的代码,比如 protobuf文件生成, 比如orm框架的model,dao等,都是需要改进的
参考
(编辑:成都站长网)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|