最近,使用到了 html/template
和 text/template
类库,通过使用模板,让项目清爽了不少。Go 的这个类似于和 PHP 的模板引擎,使用起来的话,类似于把大象关进冰箱的流程。第一步,安要求准备模板;第二步,按模板准备好数据;第三步,解析数据到模板。
定义模板
当然不使用模板当然也可以,使用 fmt.Sprintf()
同样可以实现。但应对复杂的数据模板时,fmt.Sprintf()
可能会比较麻烦。比如,我们要准备下面这样的一个模板消息,用于查看每周的值班信息。这种值班通知很常见,多见于一些工作群的群发消息。
| 时间 | 第一值班人 | 第二值班人 |
| :----- | :---- | :---- |
| 20210401 | Lucy | LiLi |
| 20210402 | Neojos | Tom |
使用 text/template
实现的过程,定义模板变量如下。代码中使用到了反引号,使用反引号定义字符串会保持字符串的原始格式。模板中的中划线表示清除空格的含义,写好模板非常简单,就是几个正常的模板关键字,比如条件判断:{{ if }} {{ else }} {{ end }}
。下面是在模板中循环遍历的模板。
var Template = `
| 时间 | 第一值班人 | 第二值班人 |
| :----- | :---- | :---- |
{{- range . }}
| {{ .Date}} {{range .Persons}} | @{{.}} {{end}} |
{{- end -}}`
构造数据
传递给模板的数据可以是数组、切片、map、或者 struct等,只要和模板中的占位解析能一一对应上就可以。按照上面的模板,我们只需要构造一个特定类型的切片结构,包含值班日期和值班人两个信息,其中值班人又区分主备,所以,值班人的类型也是一个切片。
type RowUnit struct {
Date string
Persons []string
}
// 简化值班的数据,假设 rows 里全是数据,其中 RowUnit.Persons 有且只有2个用户名称
rows := []*RowUnit{}
解析模板
代码中解析模板,变量 text
是模板解析后的内容。方法 New 用来创建模板,方法 Execute 用来将数据解析到模板 Execute 的第一个类型是 io.Writer,会将模板解析后的结构写入到这个参数中,所以,最终 text 中会存储数据绑定模板后的信息。
t, err := template.New("duty").Parse(Template)
if err != nil {
return
}
text := new(bytes.Buffer)
err = t.Execute(text, users)
模板函数
模板使用的过程非常简单,使用的时候,关键就在于多熟悉它的用法,①如何在模板中展示一个字段;②如何使用一些条件判断、循环语句。下面我们来看一下模板函数,简单来说,就是预置的一些函数用法。
拿最常见的就是比较函数来说,我们在上面的例子扩展一下比较的功能,其中 eq 就是内置的模板函数,用来做两个数据的比较。你要是写成 == 的比较方式,编译模板就会报错误信息。从这个例子中,也可以看出模板函数的用法,函数名后面跟参数,参数和参数之间使用空格分割。
| 时间 | 第一值班人 | 第二值班人 |
| :----- | :---- | :---- |
{{- range . }}
{{ if eq .Date "20210201" }} {{.Date}} {{ end }} {{range .Persons}} | @{{.}} {{end}} |
{{- end -}}`
除了模板函数,我们也可以自定义函数,然后在模板中引用自定义的函数。其实,函数的返回值是一个关键,函数是不是只可以有一个返回值,如果要返回 error 该如何处理,我们可以通过一个例子来看一下。当然,我们先看看如何将自定义函数注册到模板:
下面声明了一个 workStatus 函数,它的功能就是:通过传递的名称来判断当前员工的工作状态。在创建模板的时候调用 Funcs 来传递,主要看 FunMap 的类型,在函数的注释中有详细的介绍,函数可以返回一个返回值,或者函数返回一个返回值和一个 error,其他情况的返回是不允许的。
// FuncMap is the type of the map defining the mapping from names to functions.
// Each function must have either a single return value, or two return values of
// which the second has type error. In that case, if the second (error)
// return value evaluates to non-nil during execution, execution terminates and
// Execute returns that error.
type FuncMap map[string]any
funcMap := template.FuncMap{
"workStatus": func(name string) string {
if name == "chen" {
return "陈(已离职)"
}
return name
},
}
t, err := template.New("duty").Funcs(funcMap).Parse(Template)
if err != nil {
}