模板中是能够使用该结构体的方法
在 Go 语言的 html/template 里,要是传递的数据是结构体,那么在模板中是能够使用该结构体的方法的。
package main
import (
"html/template"
"log"
"os"
)
// Person 定义一个结构体
type Person struct {
Name string
Age int
}
// GetGreeting 定义结构体的方法,返回问候语
func (p Person) GetGreeting() string {
return "Hello, my name is " + p.Name + " and I'm " +
func() string {
if p.Age < 18 {
return "a minor"
}
return "an adult"
}() + "."
}
func main() {
// 定义模板内容
tmplStr := `{{define "personTemplate"}}{{.GetGreeting}}{{end}}`
// 创建并解析模板
tmpl, err := template.New("personTemplate").Parse(tmplStr)
if err != nil {
log.Fatalf("解析模板时出错: %v", err)
}
// 创建结构体实例
person := Person{
Name: "Alice",
Age: 25,
}
// 执行模板
err = tmpl.ExecuteTemplate(os.Stdout, "personTemplate", person)
if err != nil {
log.Fatalf("执行模板时出错: %v", err)
}
}
当你把一个结构体实例传递给模板时,模板引擎能够访问该结构体的导出方法(即方法名首字母大写)。在模板里,可通过 . 操作符来调用这些方法。
注意事项
- 只有导出的方法(方法名首字母大写)才能在模板中被调用。
- 方法不能有参数(除非通过自定义函数的方式处理),因为模板语法本身不支持带参数的方法调用。若方法需要参数,可考虑使用自定义函数来封装该方法调用。
向模板传递自定义函数
要向模板传递自定义函数,可以使用 template.FuncMap 类型,它是一个字符串到函数的映射。下面是一个示例代码,展示了如何定义和传递自定义函数
package main
import (
"html/template"
"log"
"os"
)
// 自定义函数:将字符串转换为大写
func toUpper(s string) string {
return "HELLO: " + s
}
func main() {
// 定义模板内容
tmplStr := `{{define "example"}}{{toUpper .}}{{end}}`
// 创建一个自定义函数映射
funcMap := template.FuncMap{
"toUpper": toUpper,
}
// 创建一个新的模板并添加自定义函数
tmpl, err := template.New("example").Funcs(funcMap).Parse(tmplStr)
if err != nil {
log.Fatalf("解析模板时出错: %v", err)
}
// 定义要传递给模板的数据
data := "world"
// 执行模板
err = tmpl.ExecuteTemplate(os.Stdout, "example", data)
if err != nil {
log.Fatalf("执行模板时出错: %v", err)
}
}
- 自定义函数定义:
toUpper函数接收一个字符串参数并将其转换为大写。 - 创建
FuncMap:template.FuncMap是一个字符串到函数的映射,键是函数名,值是对应的函数。 - 添加自定义函数到模板:使用
Funcs方法将FuncMap添加到模板中。 - 解析和执行模板:使用
Parse方法解析模板字符串,然后使用ExecuteTemplate方法执行模板。
html/template 包中的内置函数
比较函数
eq:判断两个值是否相等。ne:判断两个值是否不相等。lt:判断左边的值是否小于右边的值。le:判断左边的值是否小于等于右边的值。gt:判断左边的值是否大于右边的值。ge:判断左边的值是否大于等于右边的值。
逻辑函数
and:逻辑与操作,所有参数为真时返回真。or:逻辑或操作,只要有一个参数为真就返回真。not:逻辑非操作,对参数取反。
字符串处理函数
len:返回字符串、切片、数组、映射等的长度。print:格式化输出值,类似于 Go 语言中的fmt.Sprint。printf:格式化输出值,类似于 Go 语言中的fmt.Sprintf。println:格式化输出值并换行,类似于 Go 语言中的fmt.Sprintln。
其他函数
index:用于访问切片、数组、映射或字符串的元素。slice:用于创建切片。
package main
import (
"html/template"
"log"
"os"
)
func main() {
// 定义模板内容,使用多种内置函数
tmplStr := `
{{define "example"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Built-in Functions Example</title>
</head>
<body>
<!-- 使用 len 函数获取切片长度 -->
<p>Slice length: {{len .Numbers}}</p>
<!-- 使用 index 函数访问切片元素 -->
<p>First number: {{index .Numbers 0}}</p>
<!-- 使用 eq 函数进行比较 -->
<p>Is 1 equal to the first number? {{eq (index .Numbers 0) 1}}</p>
<!-- 使用 and 函数进行逻辑与操作 -->
<p>Is 1 equal to the first number and 2 equal to the second number? {{and (eq (index .Numbers 0) 1) (eq (index .Numbers 1) 2)}}</p>
<!-- 使用 printf 函数格式化输出 -->
<p>Formatted string: {{printf "The first number is %d" (index .Numbers 0)}}</p>
</body>
</html>
{{end}}`
// 创建并解析模板
tmpl, err := template.New("example").Parse(tmplStr)
if err != nil {
log.Fatalf("解析模板时出错: %v", err)
}
// 定义要传递给模板的数据
data := struct {
Numbers []int
}{
Numbers: []int{1, 2, 3, 4, 5},
}
// 执行模板
err = tmpl.ExecuteTemplate(os.Stdout, "example", data)
if err != nil {
log.Fatalf("执行模板时出错: %v", err)
}
}
综合案例:博客文章列表页
我们模拟一个博客系统,展示文章列表,每篇文章有标题、内容、发布时间、作者。
模板中将使用:
- 自定义函数:
formatTime(格式化时间)、truncate(截断字符串) - 内置函数:
slice(切片前3篇文章) - 结构体方法:
Article.Summary()获取摘要、Article.IsNew()判断是否为新文章
1. Go 代码 (main.go)
package main
import (
"html/template"
"log"
"net/http"
"strings"
"time"
)
// Article 文章结构体
type Article struct {
Title string
Content string
Author string
PubTime time.Time
}
// Summary 获取文章摘要(结构体方法)
func (a Article) Summary() string {
if len(a.Content) > 100 {
return a.Content[:100] + "..."
}
return a.Content
}
// IsNew 判断是否为7天内发布的新文章(结构体方法)
func (a Article) IsNew() bool {
return time.Since(a.PubTime) < 7*24*time.Hour
}
// 自定义模板函数
var funcMap = template.FuncMap{
"formatTime": func(t time.Time) string {
return t.Format("2006-01-02 15:04")
},
"truncate": func(s string, n int) string {
if len(s) <= n {
return s
}
return s[:n] + "..."
},
}
func main() {
// 创建模板,注册自定义函数
tmpl := template.Must(template.New("blog.html").Funcs(funcMap).ParseFiles("blog.html"))
// 模拟数据
articles := []Article{
{
Title: "Go语言入门指南",
Content: strings.Repeat("Go是一门强大的编程语言。", 10),
Author: "张三",
PubTime: time.Now().Add(-2 * 24 * time.Hour), // 2天前
},
{
Title: "深入理解Goroutine",
Content: strings.Repeat("并发是Go的核心特性。", 15),
Author: "李四",
PubTime: time.Now().Add(-10 * 24 * time.Hour), // 10天前
},
{
Title: "Go Template完全指南",
Content: strings.Repeat("模板引擎非常实用。", 20),
Author: "王五",
PubTime: time.Now().Add(-1 * time.Hour), // 1小时前
},
{
Title: "Go与微服务架构",
Content: strings.Repeat("微服务是现代架构趋势。", 12),
Author: "赵六",
PubTime: time.Now().Add(-5 * 24 * time.Hour), // 5天前
},
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
data := struct {
Title string
Articles []Article
}{
Title: "我的博客",
Articles: articles,
}
err := tmpl.Execute(w, data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
log.Println("Server running on http://localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
2. HTML 模板 (blog.html)
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>{{ .Title }}</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.article { border: 1px solid #ccc; margin: 20px 0; padding: 20px; border-radius: 5px; }
.new-badge { background: #ff6b6b; color: white; padding: 3px 8px; border-radius: 3px; font-size: 12px; }
.summary { color: #555; margin: 10px 0; }
.meta { font-size: 14px; color: #888; }
</style>
</head>
<body>
<h1>{{ .Title }}</h1>
<h2>最新三篇文章(使用 slice)</h2>
{{ $top3 := slice .Articles 0 3 }}
{{ range $top3 }}
<div class="article">
<h3>{{ .Title }}
{{ if .IsNew }} <!-- 调用结构体方法 -->
<span class="new-badge">NEW</span>
{{ end }}
</h3>
<div class="summary">
{{ .Summary }} <!-- 调用结构体方法 -->
<!-- 或者用自定义函数截断 -->
<!-- {{ truncate .Content 80 }} -->
</div>
<div class="meta">
作者:{{ .Author }} | 发布:{{ .PubTime | formatTime }} <!-- 自定义函数 -->
</div>
</div>
{{ else }}
<p>暂无文章。</p>
{{ end }}
<hr>
<h2>全部文章(使用自定义 truncate 函数)</h2>
{{ range .Articles }}
<div class="article">
<h3>{{ .Title }}</h3>
<div class="summary">
{{ truncate .Content 60 }} <!-- 自定义函数截断内容 -->
</div>
<div class="meta">
作者:{{ .Author }} | 发布:{{ .PubTime | formatTime }}
</div>
</div>
{{ end }}
</body>
</html>
3. 运行方式
- 将 Go 代码保存为
main.go - 将 HTML 代码保存为
blog.html(与main.go同目录) - 终端运行:
go run main.go
- 打开浏览器访问:http://localhost:8080
效果说明
- 页面显示“最新三篇文章”,使用
slice .Articles 0 3 - 每篇文章调用结构体方法
.IsNew()判断是否显示“NEW”标签 - 调用
.Summary()方法显示摘要(也可替换为truncate自定义函数) - 使用
formatTime自定义函数格式化时间 - 使用
truncate自定义函数在第二部分截断内容展示
知识点总结
| 类型 | 示例 | 说明 |
|---|---|---|
| 自定义函数 | formatTime, truncate | 通过 FuncMap 注册 |
| 内置函数 | slice .Articles 0 3 | 截取数组/切片 |
| 结构体方法 | .IsNew(), .Summary() | 在模板中直接调用 |
| 管道操作符 | {{ .PubTime | formatTime }} | 将值传给函数 |
| 条件判断 | {{ if .IsNew }} ... {{ end }} | 控制显示逻辑 |
| 循环 | {{ range ... }} | 遍历切片 |
❓ {{ .PubTime | formatTime }} 能不能写成 {{ formatTime .PubTime }}?
✅ 答案:可以!两者完全等价,功能一模一样。
两种写法的区别(仅语法风格不同)
| 写法 | 名称 | 说明 |
|---|---|---|
{{ .PubTime | formatTime }} | 管道风格(Pipeline) | 把 .PubTime 作为参数“管道”传给 formatTime 函数,类似 Unix shell 的管道 x | f |
{{ formatTime .PubTime }} | 函数调用风格 | 直接调用函数,把参数写在函数名后面,类似常规编程语言 |
示例对比
// 模板中这两种写法效果完全相同:
{{ .PubTime | formatTime }}
{{ formatTime .PubTime }}
都会调用:
funcMap["formatTime"] = func(t time.Time) string {
return t.Format("2006-01-02 15:04")
}
传入 .PubTime,返回格式化后的时间字符串。
什么时候用哪种风格?
👉 推荐使用 管道风格 {{ .Value | func }} 的场景:
-
链式操作(多个函数依次处理):
{{ .Content | truncate 50 | title }}等价于:
{{ title (truncate .Content 50) }}→ 管道写法更清晰、可读性更强!
-
和内置动作结合:
{{ if .Title | len }} ... {{ end }}
👉 推荐使用 函数调用风格 {{ func .Value }} 的场景:
-
只调用一次函数,无链式操作
-
函数有多个参数(管道只能传第一个参数):
{{ truncate .Content 30 }} ✅ 正确 {{ .Content | truncate 30 }} ❌ 错误!管道只能传第一个参数
⚠️ 注意:管道操作符只能传递一个参数(作为函数的第一个参数) ,其余参数必须用函数调用风格!