深色模式
Go 模板基础
概述
Go 的模板系统有两个入口:text/template 和 html/template。前者适合生成普通文本,后者适合生成 HTML。它们语法几乎一样,但安全语义不一样,这一点不能混着看。
模板语法本身不复杂,真正容易混乱的是三个问题:点号 . 到底代表什么、函数和管道怎么串、多个模板文件怎么组合。把这三件事搞清楚,模板这块就基本够用了。
text/template 和 html/template 的区别
text/template:输出普通文本,不做 HTML 安全转义。html/template:面向 HTML 输出,会对危险内容做自动转义。
如果输出目标是 HTML 页面,默认应该优先选 html/template。这不是风格问题,而是安全边界问题。
点号 . 是当前数据上下文
模板里最基础的动作是读取当前上下文:
txt
{{.}}
{{.Name}}
{{.Age}}假设传入的数据是:
go
type User struct {
Name string
Age int
}那模板可以这样写:
txt
姓名:{{.Name}}
年龄:{{.Age}}配合代码:
go
tpl := template.Must(template.New("user").Parse(`
姓名:{{.Name}}
年龄:{{.Age}}
`))
_ = tpl.Execute(os.Stdout, User{Name: "Tom", Age: 18})条件、循环和 with
模板控制流最常用的就是 if、range、with。
if
txt
{{if .VIP}}
欢迎回来,老朋友
{{else}}
欢迎注册
{{end}}range
txt
{{range .Items}}
- {{.}}
{{end}}如果想同时拿到索引和值:
txt
{{range $i, $item := .Items}}
{{$i}}: {{$item}}
{{end}}with
with 的作用是切换当前上下文,写嵌套数据时会更清楚:
txt
{{with .Author}}
作者:{{.Name}}
{{end}}进入 with 之后,里面的 . 就不再是外层对象,而是 .Author。
管道会让模板更顺手
管道和 shell 里的概念有点像,作用是把前一个结果传给后一个函数。
txt
{{.Name | printf "Hello, %s"}}如果要自定义函数,可以在解析模板前挂上 FuncMap:
go
funcMap := template.FuncMap{
"upper": strings.ToUpper,
}
tpl := template.Must(
template.New("demo").
Funcs(funcMap).
Parse(`{{upper .Name}}`),
)这个顺序不能反。Funcs 必须在 Parse 之前调用,不然模板里用到的函数会找不到。
HTML 模板默认会转义
这是 html/template 最重要的行为之一。假设数据里有一段用户输入:
go
data := struct {
Content string
}{
Content: `<script>alert("xss")</script>`,
}模板:
txt
<div>{{.Content}}</div>用 html/template 渲染时,这段内容会被转义成普通文本,而不是原样执行。这个默认行为非常重要,别为了图省事换回 text/template。
只有在你明确知道内容已经是可信 HTML 时,才应该显式使用 template.HTML 这类类型。否则就是主动把 XSS 的门打开。
模板组合比复制粘贴更稳
页面或文本结构稍微复杂一点时,通常会拆成多个命名模板。
txt
{{define "header"}}<h1>{{.Title}}</h1>{{end}}
{{define "body"}}<p>{{.Body}}</p>{{end}}
{{define "page"}}
{{template "header" .}}
{{template "body" .}}
{{end}}执行时渲染 page 即可:
go
tpl := template.Must(template.New("page").ParseFiles("header.tmpl", "body.tmpl", "page.tmpl"))
_ = tpl.ExecuteTemplate(w, "page", data)模板一旦拆开,页面结构、邮件内容、配置文件生成逻辑都会更容易维护。
什么时候模板不再合适
模板很适合:
- 生成 HTML
- 生成文本配置
- 生成邮件正文
- 生成代码片段
但如果模板里开始塞大量业务判断、复杂循环和状态分支,就该停一下。那通常不是模板太弱,而是业务逻辑已经应该先在 Go 代码里整理好,再把结果交给模板负责展示。
