html/template 不支持 extends 语法,需用 define + template 手动实现继承;所有模板必须一次性 ParseFiles 到同一 Template 实例,渲染时 Execute 父模板(如 layout.html),子模板仅定义区块。

html/template 本身不支持 extends 语法,必须手动组合 define + template
Go 标准库的 html/template 没有 {% extends %} 或 {% block %} 这类原生继承指令。所谓“模板继承”,本质是靠 {{define}} 声明命名区块、{{template "name" .}} 渲染区块,并确保所有相关模板在**同一个 *template.Template 实例中解析完成**。一旦漏掉某个 {{define}} 文件,执行时就会 panic:template: "content" is not defined。
常见错误现象:
- 用
template.ParseFiles("layout.html")后再ParseFiles("home.html")—— 第二次调用会覆盖第一次,导致layout.html里的{{define "header"}}丢失 - 子模板里写了
{{define "content"}}...{{end}},却对子模板调用t.Execute()—— 结果只渲染了 content 区块,整个 layout 结构全没了 - 多个子模板都
{{define "content"}},但用ParseFiles顺序加载,后加载的覆盖前加载的 —— 导致只有最后一个页面生效
正确加载方式:所有模板文件必须一次性 ParseFiles 到同一实例
关键不是“先 layout 后 page”,而是让所有 {{define}} 全部注册进同一个模板树。最稳妥的做法是显式列出全部路径:
t := template.New("master")
t = template.Must(t.ParseFiles(
"views/layout.html",
"views/home.html",
"views/users/list.html",
"views/users/edit.html",
))注意点:
立即学习“go语言免费学习笔记(深入)”;
-
template.Must()返回的是原实例,不是新对象,所以可以链式调用 - 文件路径必须真实存在且可读;重复
ParseFiles同一内容会覆盖已有define - 如果模板分散在多级目录,别用
ParseGlob("views/**/*")—— Go 1.16+ 的filepath.Glob不保证遍历顺序,容易引发 define 覆盖问题
渲染时必须 Execute 父模板,不能 Execute 子模板
职责要分清:layout.html 负责整体结构和 {{template "content" .}} 占位,home.html 只负责 {{define "content"}} 和 {{define "title"}} 等局部内容。最终执行的是 layout,不是 home。
典型结构:
-
layout.html:含{{template "header" .}}、{{template "content" .}}、{{template "footer" .}} -
home.html:只含{{define "content"}}<h1>首页</h1>{{end}}{{define "title"}}首页 - MyApp{{end}} - Go 代码中调用
t.Execute(w, data)—— 此时t是包含 layout.html 的实例,它会自动找到 home.html 里定义的"content"
如果想按页面名动态渲染(比如 /users 渲染 users/list.html),要用 t.ExecuteTemplate(w, "users/list.html", data),前提是该文件名已作为模板名注册进实例(即被 ParseFiles 加载过)。
多页面共用 layout 时,define 冲突的规避方法
{{define}} 是全局注册的,同名会覆盖 —— 这不是 bug,是设计如此。但实际开发中,你不可能让所有页面共享一个 "content" 定义。解决思路有两个:
- 用唯一前缀:把
home.html里的{{define "content"}}改成{{define "home_content"}},并在layout.html中写{{template "home_content" .}}—— 简单直接,适合页面数少、结构差异大的场景 - 用统一入口模板:新建
pages/home.html,内容为{{template "layout.html" .}}{{define "content"}}...{{end}},然后只加载这个文件 —— 更接近 Django 风格,但需确保layout.html已提前注册
真正容易被忽略的是:模板复用的前提是「启动时一次性加载」。如果在 handler 里每次请求都 ParseFiles,不仅性能差,还会因并发导致 define 注册状态不可预测。务必把模板加载放在 main() 或初始化函数中,作为全局变量复用。



















