<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Template on 山高月小</title><link>https://blog.dart.xin/tags/template/</link><description>Recent content in Template on 山高月小</description><generator>Hugo</generator><language>zh-cn</language><copyright>zwdart</copyright><lastBuildDate>Tue, 13 Oct 2026 11:05:20 +0800</lastBuildDate><atom:link href="https://blog.dart.xin/tags/template/index.xml" rel="self" type="application/rss+xml"/><item><title>Template 系统性学习</title><link>https://blog.dart.xin/golang/go107-template/</link><pubDate>Tue, 13 Oct 2026 11:05:20 +0800</pubDate><guid>https://blog.dart.xin/golang/go107-template/</guid><description>服务端渲染和数据驱动的文本生成工具</description><content:encoded><![CDATA[<h1 id="go-template-系统性学习指南">Go Template 系统性学习指南</h1>
<p>Go 标准库的模板引擎是服务端渲染和数据驱动的文本生成工具，核心设计理念是<strong>将表现层与数据模型分离</strong>。Go 用同一套语法提供了两个包：<code>text/template</code> 用于通用文本生成，<code>html/template</code> 用于 HTML 输出并自带上下文感知的自动转义安全机制。网络服务中生成 HTML 必须使用 <code>html/template</code>。</p>
<h2 id="一基础用法三步流程">一、基础用法：三步流程</h2>
<p>模板使用的核心流程固定为三步：<strong>定义/解析模板 → 准备数据 → 执行渲染</strong>。</p>
<h3 id="11-从字符串解析">1.1 从字符串解析</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;os&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;text/template&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">Inventory</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Material</span><span class="w"> </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Count</span><span class="w">    </span><span class="kt">uint</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 步骤1：解析模板</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;test&#34;</span><span class="p">).</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">Parse</span><span class="p">(</span><span class="s">&#34;{{.Count}} items are made of {{.Material}}&#34;</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 步骤2：准备数据</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">sweaters</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">Inventory</span><span class="p">{</span><span class="s">&#34;wool&#34;</span><span class="p">,</span><span class="w"> </span><span class="mi">17</span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 步骤3：执行渲染</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">,</span><span class="w"> </span><span class="nx">sweaters</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// 输出：17 items are made of wool</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>template.Must</code> 是对 <code>Parse</code> 错误的 panic 包装，适合在包初始化阶段使用。<strong>模板中只能访问结构体的导出字段（首字母大写）</strong>，小写字段在模板侧不可达，会导致执行期报错或呈现零值。</p>
<h3 id="12-从文件解析">1.2 从文件解析</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// 解析单个文件</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">ParseFiles</span><span class="p">(</span><span class="s">&#34;templates/layout.html&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 解析多个文件（常用于主模板+子模板）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">ParseFiles</span><span class="p">(</span><span class="s">&#34;templates/layout.html&#34;</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;templates/header.html&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 使用 Glob 模式批量解析</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">ParseGlob</span><span class="p">(</span><span class="s">&#34;templates/*.html&#34;</span><span class="p">)</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="13-数据访问方式">1.3 数据访问方式</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// 结构体字段访问</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="kd">struct</span><span class="p">{</span><span class="w"> </span><span class="nx">Name</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">}{</span><span class="s">&#34;Alice&#34;</span><span class="p">})</span><span class="w">  </span><span class="c1">// {{.Name}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// map 键访问（无大小写限制）</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="kd">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span><span class="s">&#34;name&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;Bob&#34;</span><span class="p">})</span><span class="w"> </span><span class="c1">// {{.name}} 或 {{.Name}} 均可</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 点（.）表示当前作用域的值</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 在根模板中，{{.}} 就是 Execute 传入的整个数据</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h2 id="二模板语法核心">二、模板语法核心</h2>
<h3 id="21-注释">2.1 注释</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="cm">/* 这是注释，不会输出 */</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="o">-</span><span class="w"> </span><span class="cm">/* 注释，同时修剪前后空白 */</span><span class="w"> </span><span class="o">-</span><span class="p">}}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="22-条件判断">2.2 条件判断</h3>
<p>Go 模板中，以下值被视为“假”：<code>false</code>、<code>0</code>、<code>nil</code>、空字符串、空切片/映射/数组。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="k">if</span><span class="w"> </span><span class="p">.</span><span class="nx">Attended</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">很高兴您能参加婚礼</span><span class="err">。</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="k">else</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">很遗憾您未能参加</span><span class="err">。</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="nx">end</span><span class="p">}}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>if</code> 支持 <code>else if</code>，也常与内置比较函数配合：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="k">if</span><span class="w"> </span><span class="nx">eq</span><span class="w"> </span><span class="p">.</span><span class="nx">Status</span><span class="w"> </span><span class="s">&#34;active&#34;</span><span class="p">}}</span><span class="w"> </span><span class="nx">活跃</span><span class="w"> </span><span class="p">{{</span><span class="k">else</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="nx">gt</span><span class="w"> </span><span class="p">.</span><span class="nx">Score</span><span class="w"> </span><span class="mi">90</span><span class="p">}}</span><span class="w"> </span><span class="nx">优秀</span><span class="w"> </span><span class="p">{{</span><span class="nx">end</span><span class="p">}}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>常用比较函数：<code>eq</code>（等于）、<code>ne</code>（不等于）、<code>lt</code>（小于）、<code>gt</code>（大于）、<code>le</code>（小于等于）、<code>ge</code>（大于等于）。</p>
<h3 id="23-循环-range">2.3 循环 range</h3>
<p><code>range</code> 支持 slice、array、map、channel。内部 <code>{{.}}</code> 为当前元素；可通过变量捕获索引/键和值。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// 遍历切片：下标 + 值</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="k">range</span><span class="w"> </span><span class="err">$</span><span class="nx">idx</span><span class="p">,</span><span class="w"> </span><span class="err">$</span><span class="nx">email</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">.</span><span class="nx">Emails</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">第</span><span class="w"> </span><span class="p">{{</span><span class="err">$</span><span class="nx">idx</span><span class="p">}}</span><span class="w"> </span><span class="nx">项</span><span class="p">:</span><span class="w"> </span><span class="p">{{</span><span class="err">$</span><span class="nx">email</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="nx">end</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 遍历 map：key + value</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="k">range</span><span class="w"> </span><span class="err">$</span><span class="nx">key</span><span class="p">,</span><span class="w"> </span><span class="err">$</span><span class="nx">value</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">.</span><span class="nx">MapContent</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">{{</span><span class="err">$</span><span class="nx">key</span><span class="p">}}:</span><span class="w"> </span><span class="p">{{</span><span class="err">$</span><span class="nx">value</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="nx">end</span><span class="p">}}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><code>range</code> 在空集合时不执行，<code>. </code> 不会改变。若只需值，使用 <code>{{range .Items}}</code> 即可。</p>
<h3 id="24-with-改变作用域">2.4 with 改变作用域</h3>
<p><code>with</code> 将点临时切换到某个值，适合简化深层字段访问：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="nx">with</span><span class="w"> </span><span class="p">.</span><span class="nx">Gift</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">感谢您送的</span><span class="w"> </span><span class="p">{{.}}</span><span class="err">。</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="nx">end</span><span class="p">}}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>若 <code>.Gift</code> 为“假值”，<code>with</code> 块不执行。</p>
<h3 id="25-变量">2.5 变量</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="err">$</span><span class="nx">v</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">.</span><span class="nx">SomeValue</span><span class="p">}}</span><span class="w">      </span><span class="p">{{</span><span class="cm">/* 声明并赋值 */</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="err">$</span><span class="nx">v</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">&#34;new value&#34;</span><span class="p">}}</span><span class="w">      </span><span class="p">{{</span><span class="cm">/* 重新赋值（Go 1.11+） */</span><span class="p">}}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>变量在 <code>range</code> 和 <code>with</code> 改变点作用域后仍然可读，这是保留根数据引用的常用手段。</p>
<h3 id="26-管道">2.6 管道</h3>
<p>管道语义类似 UNIX：<code>{{ a | f | g }}</code> 等价于 <code>g(f(a))</code>。前一个函数的结果作为下一个函数的<strong>最后一个参数</strong>。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="p">.</span><span class="nx">Personal</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nx">printf</span><span class="w"> </span><span class="s">&#34;[%s]&#34;</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="w"> </span><span class="p">.</span><span class="nx">Name</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nx">upper</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nx">printf</span><span class="w"> </span><span class="s">&#34;用户: %s&#34;</span><span class="w"> </span><span class="p">}}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>管道让展示层的轻量格式化（如 <code>printf</code>）保持在模板内，避免把“仅为排版服务”的逻辑塞进业务层。</p>
<h3 id="27-空白修剪">2.7 空白修剪</h3>
<p>模板中的换行和缩进会原样输出，这在生成代码或结构化文本时往往需要精确控制。</p>
<ul>
<li><code>{{-</code> ：修剪<strong>左侧</strong>紧邻文本的尾部空白</li>
<li><code>-}}</code> ：修剪<strong>右侧</strong>紧邻文本的前导空白</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// 不修剪时，range 循环会引入多余换行</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="k">range</span><span class="w"> </span><span class="p">.</span><span class="nx">Items</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">{{.}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="nx">end</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 双向修剪，保持紧凑输出</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="o">-</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="p">.</span><span class="nx">Items</span><span class="w"> </span><span class="o">-</span><span class="p">}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">{{.}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">{{</span><span class="o">-</span><span class="w"> </span><span class="nx">end</span><span class="w"> </span><span class="o">-</span><span class="p">}}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>注意：<code>{{- 3}}</code> 修剪左侧空白后解析为数字 <code>3</code>；而 <code>{{-3}}</code> 会被解析为<strong>负数 -3</strong> 的操作。修剪符后的空格必须存在。</p>
<h2 id="三自定义函数">三、自定义函数</h2>
<p><code>template.FuncMap</code> 用于将 Go 函数注册到模板命名空间中。<strong>必须先调用 <code>Funcs()</code> 注册函数，再调用 <code>Parse()</code></strong>，否则 AST 中出现的未知标识符会导致 Parse 失败。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">funcMap</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nx">FuncMap</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;upper&#34;</span><span class="p">:</span><span class="w">      </span><span class="nx">strings</span><span class="p">.</span><span class="nx">ToUpper</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;formatDate&#34;</span><span class="p">:</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">t</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Time</span><span class="p">)</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">t</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">&#34;2006-01-02&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;safeHTML&#34;</span><span class="p">:</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">s</span><span class="w"> </span><span class="kt">string</span><span class="p">)</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nx">HTML</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">HTML</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span><span class="w">  </span><span class="c1">// 慎重使用！</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;example&#34;</span><span class="p">).</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">Funcs</span><span class="p">(</span><span class="nx">funcMap</span><span class="p">).</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">Parse</span><span class="p">(</span><span class="s">`</span><span class="cp">{{</span><span class="w"> </span><span class="na">.User</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">upper</span><span class="w"> </span><span class="cp">}}</span><span class="s"> - </span><span class="cp">{{</span><span class="w"> </span><span class="na">.Time</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">formatDate</span><span class="w"> </span><span class="cp">}}</span><span class="s">`</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>自定义函数应只做<strong>展示层的纯格式化或轻量映射</strong>（日期格式、单位转换、字符串处理）。复杂分支、聚合和权限决策应放在 Go 代码中，不要塞入模板。</p>
<h2 id="四嵌套模板与布局">四、嵌套模板与布局</h2>
<h3 id="41-define--template--block">4.1 define / template / block</h3>
<p><code>{{define &quot;name&quot;}}</code> 定义命名模板，<code>{{template &quot;name&quot; .}}</code> 调用它并传入数据。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">layout</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="k">define</span><span class="w"> </span><span class="s">&#34;base&#34;</span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;!DOCTYPE html&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;html&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;head&gt;&lt;title&gt;</span><span class="cp">{{</span><span class="na">.Title</span><span class="cp">}}</span><span class="s">&lt;/title&gt;&lt;/head&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;body&gt;</span><span class="cp">{{</span><span class="k">template</span><span class="w"> </span><span class="s">&#34;content&#34;</span><span class="w"> </span><span class="na">.</span><span class="cp">}}</span><span class="s">&lt;/body&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;/html&gt;
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="k">end</span><span class="cp">}}</span><span class="s">`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">page</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="k">define</span><span class="w"> </span><span class="s">&#34;content&#34;</span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;h1&gt;</span><span class="cp">{{</span><span class="na">.Heading</span><span class="cp">}}</span><span class="s">&lt;/h1&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;p&gt;</span><span class="cp">{{</span><span class="na">.Body</span><span class="cp">}}</span><span class="s">&lt;/p&gt;
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="k">end</span><span class="cp">}}</span><span class="s">`</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p><strong>加载顺序很重要</strong>：Go 模板引擎按加载顺序查找模板，如果多个文件定义了同名模板，<strong>只有第一个被使用</strong>。因此不同页面的内容模板应使用不同的名称。</p>
<h3 id="42-block-用于可覆盖的默认实现">4.2 block 用于可覆盖的默认实现</h3>
<p><code>{{block &quot;name&quot; .}}...{{end}}</code> 等价于“定义默认模板 + 调用它”。子模板可以通过 <code>{{define &quot;name&quot;}}</code> 覆盖默认实现。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// 主模板</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">master</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`Names:</span><span class="cp">{{</span><span class="k">block</span><span class="w"> </span><span class="s">&#34;list&#34;</span><span class="w"> </span><span class="na">.</span><span class="cp">}}{{</span><span class="k">range</span><span class="w"> </span><span class="na">.</span><span class="cp">}}{{</span><span class="k">println</span><span class="w"> </span><span class="s">&#34;-&#34;</span><span class="w"> </span><span class="na">.</span><span class="cp">}}{{</span><span class="k">end</span><span class="cp">}}{{</span><span class="k">end</span><span class="cp">}}</span><span class="s">`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 子模板覆盖</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">const</span><span class="w"> </span><span class="nx">overlay</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`</span><span class="cp">{{</span><span class="k">define</span><span class="w"> </span><span class="s">&#34;list&#34;</span><span class="cp">}}{{</span><span class="nx">join</span><span class="w"> </span><span class="na">.</span><span class="w"> </span><span class="s">&#34;, &#34;</span><span class="cp">}}{{</span><span class="k">end</span><span class="cp">}}</span><span class="s">`</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>调用 <code>masterTmpl.Execute</code> 输出默认列表；克隆后解析 overlay 再执行，则输出用逗号连接的结果。</p>
<h3 id="43-gin-框架中的嵌套模板">4.3 Gin 框架中的嵌套模板</h3>
<p>在 Gin 中，通常用 <code>LoadHTMLGlob</code> 加载所有模板，配合 <code>{{define}}</code> 组织布局：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="nx">r</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">gin</span><span class="p">.</span><span class="nf">Default</span><span class="p">()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">r</span><span class="p">.</span><span class="nf">LoadHTMLGlob</span><span class="p">(</span><span class="s">&#34;templates/**/*&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// templates/layout/base.html</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// {{define &#34;base&#34;}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// &lt;html&gt;&lt;head&gt;&lt;title&gt;{{.title}}&lt;/title&gt;&lt;/head&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// &lt;body&gt;{{template .content .}}&lt;/body&gt;&lt;/html&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// {{end}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// templates/pages/home.html</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// {{define &#34;home-content&#34;}}&lt;p&gt;首页内容&lt;/p&gt;{{end}}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 处理器中</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">c</span><span class="p">.</span><span class="nf">HTML</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusOK</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;base&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">gin</span><span class="p">.</span><span class="nx">H</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;title&#34;</span><span class="p">:</span><span class="w">   </span><span class="s">&#34;Home&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;content&#34;</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;home-content&#34;</span><span class="p">,</span><span class="w">  </span><span class="c1">// 动态指定要渲染的子模板名</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">})</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>如果子模板名称是动态传入的变量，标准的 <code>{{template .content .}}</code> 语法不支持变量作为模板名，需要通过自定义函数（如示例中的 <code>DynamicTemplate</code>）来间接实现。</p>
<h2 id="五html-安全htmltemplate-的自动转义">五、HTML 安全：html/template 的自动转义</h2>
<p><code>html/template</code> 与 <code>text/template</code> 共享 API，但增加了<strong>上下文感知的自动转义</strong>。这是 Go 模板最重要的安全特性。</p>
<h3 id="51-自动转义演示">5.1 自动转义演示</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// text/template：直接输出，产生 XSS</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;foo&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="s">`Hello, </span><span class="cp">{{</span><span class="na">.</span><span class="cp">}}</span><span class="s">!`</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">out</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 输出：Hello, &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;!</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// html/template：自动转义，安全</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">,</span><span class="w"> </span><span class="nx">_</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">htmltemplate</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;foo&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="s">`Hello, </span><span class="cp">{{</span><span class="na">.</span><span class="cp">}}</span><span class="s">!`</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nx">t</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">out</span><span class="p">,</span><span class="w"> </span><span class="s">&#34;&lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">// 输出：Hello, &amp;lt;script&amp;gt;alert(&#39;xss&#39;)&amp;lt;/script&amp;gt;!</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>转义是<strong>上下文感知</strong>的：同一个 <code>{{.}}</code> 出现在 HTML 文本、属性、JavaScript、CSS、URL 中时，会应用不同的转义规则。例如：</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-html" data-lang="html"><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/search?q={{.}}&#34;</span><span class="p">&gt;</span>{{.}}<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c">&lt;!-- 模板引擎在解析时自动改写为： --&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">&lt;</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">&#34;/search?q={{. | urlescaper | attrescaper}}&#34;</span><span class="p">&gt;</span>{{. | htmlescaper}}<span class="p">&lt;/</span><span class="nt">a</span><span class="p">&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="52-安全模型">5.2 安全模型</h3>
<p><code>html/template</code> 的安全模型假设<strong>模板作者是可信的，而 Execute 传入的数据不可信</strong>。不应在模板中直接拼接用户输入。如果确实需要输出可信的 HTML 片段，使用 <code>template.HTML</code> 类型显式标记，但这<strong>绕过了自动转义</strong>，必须确保内容来源完全可控，绝不可用于用户输入。</p>
<h2 id="六性能优化">六、性能优化</h2>
<h3 id="61-核心原则启动时解析请求时只执行">6.1 核心原则：启动时解析，请求时只执行</h3>
<p><code>Parse</code> 包含 I/O 和编译成本，<strong>禁止在每个 HTTP 请求中调用 Parse</strong>。应在 <code>init()</code> 或程序启动阶段解析一次，请求路径只做 <code>Execute</code>。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="c1">// ✅ 正确：包级别初始化</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">reportTmpl</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">template</span><span class="p">.</span><span class="nf">ParseFiles</span><span class="p">(</span><span class="s">&#34;templates/report.html&#34;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">handler</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">reportTmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">data</span><span class="p">)</span><span class="w">  </span><span class="c1">// 只执行，不解析</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><h3 id="62-并发安全">6.2 并发安全</h3>
<p>模板一旦解析完成，<code>Execute</code> 可以安全地并发调用。但如果多个 goroutine 共享同一个 <code>io.Writer</code>，输出可能交错。每个请求应使用独立的 <code>Writer</code>（如 <code>http.ResponseWriter</code>）。</p>
<h3 id="63-需要不同数据隔离时使用-clone">6.3 需要不同数据隔离时使用 Clone</h3>
<p>如果需要在同一套模板基础上为不同请求做数据隔离的变体，使用 <code>Clone()</code> 创建副本。</p>
<h3 id="64-避免不必要的转义开销">6.4 避免不必要的转义开销</h3>
<p><code>html/template</code> 的自动转义会带来一定的性能开销。如果输出的是纯文本（如生成代码、配置文件），使用 <code>text/template</code> 可以避免转义成本。只有在生成 HTML 时才应使用 <code>html/template</code>。</p>
<h2 id="七测试策略">七、测试策略</h2>
<p>模板测试的核心是<strong>构造数据 → 执行 → 断言输出</strong>。推荐的模式是表驱动测试。</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">TestLetterTemplate</span><span class="p">(</span><span class="nx">t</span><span class="w"> </span><span class="o">*</span><span class="nx">testing</span><span class="p">.</span><span class="nx">T</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">const</span><span class="w"> </span><span class="nx">letter</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="s">`Dear </span><span class="cp">{{</span><span class="na">.Name</span><span class="cp">}}</span><span class="s">,</span><span class="cp">{{</span><span class="k">if</span><span class="w"> </span><span class="na">.Attended</span><span class="cp">}}</span><span class="s"> 感谢您的到来。</span><span class="cp">{{</span><span class="k">else</span><span class="cp">}}</span><span class="s"> 很遗憾您未能出席。</span><span class="cp">{{</span><span class="k">end</span><span class="cp">}}</span><span class="s">`</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">tests</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="p">[]</span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">name</span><span class="w">     </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">data</span><span class="w">     </span><span class="nx">Recipient</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">contains</span><span class="w"> </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">name</span><span class="p">:</span><span class="w">     </span><span class="s">&#34;attended&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">data</span><span class="p">:</span><span class="w">     </span><span class="nx">Recipient</span><span class="p">{</span><span class="nx">Name</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;Mildred&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">Attended</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">contains</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;感谢您的到来&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">name</span><span class="p">:</span><span class="w">     </span><span class="s">&#34;not attended&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">data</span><span class="p">:</span><span class="w">     </span><span class="nx">Recipient</span><span class="p">{</span><span class="nx">Name</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;John&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">Attended</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">contains</span><span class="p">:</span><span class="w"> </span><span class="s">&#34;很遗憾您未能出席&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">tt</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="k">range</span><span class="w"> </span><span class="nx">tests</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">t</span><span class="p">.</span><span class="nf">Run</span><span class="p">(</span><span class="nx">tt</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">t</span><span class="w"> </span><span class="o">*</span><span class="nx">testing</span><span class="p">.</span><span class="nx">T</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nx">tmpl</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;letter&#34;</span><span class="p">).</span><span class="nf">Parse</span><span class="p">(</span><span class="nx">letter</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="kd">var</span><span class="w"> </span><span class="nx">buf</span><span class="w"> </span><span class="nx">bytes</span><span class="p">.</span><span class="nx">Buffer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">buf</span><span class="p">,</span><span class="w"> </span><span class="nx">tt</span><span class="p">.</span><span class="nx">data</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nx">t</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"> </span><span class="p">!</span><span class="nx">strings</span><span class="p">.</span><span class="nf">Contains</span><span class="p">(</span><span class="nx">buf</span><span class="p">.</span><span class="nf">String</span><span class="p">(),</span><span class="w"> </span><span class="nx">tt</span><span class="p">.</span><span class="nx">contains</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nx">t</span><span class="p">.</span><span class="nf">Errorf</span><span class="p">(</span><span class="s">&#34;output %q does not contain %q&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">buf</span><span class="p">.</span><span class="nf">String</span><span class="p">(),</span><span class="w"> </span><span class="nx">tt</span><span class="p">.</span><span class="nx">contains</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">})</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>对于 HTML 模板，还应测试<strong>特殊字符是否被正确转义</strong>，以及子模板的嵌套执行是否正确。</p>
<h2 id="八实战完整-http-服务示例">八、实战：完整 HTTP 服务示例</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span><span class="lnt">56
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;html/template&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;log&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;net/http&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">type</span><span class="w"> </span><span class="nx">User</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Name</span><span class="w">      </span><span class="kt">string</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">Age</span><span class="w">       </span><span class="kt">int</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">JoinedAt</span><span class="w">  </span><span class="nx">time</span><span class="p">.</span><span class="nx">Time</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">IsPremium</span><span class="w"> </span><span class="kt">bool</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">var</span><span class="w"> </span><span class="nx">tmpl</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">template</span><span class="p">.</span><span class="nf">Must</span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">template</span><span class="p">.</span><span class="nf">New</span><span class="p">(</span><span class="s">&#34;user&#34;</span><span class="p">).</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">Funcs</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nx">FuncMap</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="s">&#34;formatDate&#34;</span><span class="p">:</span><span class="w"> </span><span class="kd">func</span><span class="p">(</span><span class="nx">t</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Time</span><span class="p">)</span><span class="w"> </span><span class="kt">string</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">return</span><span class="w"> </span><span class="nx">t</span><span class="p">.</span><span class="nf">Format</span><span class="p">(</span><span class="s">&#34;2006-01-02&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">},</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}).</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">Parse</span><span class="p">(</span><span class="s">`
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;!DOCTYPE html&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;html&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;head&gt;&lt;meta charset=&#34;UTF-8&#34;&gt;&lt;title&gt;用户信息&lt;/title&gt;&lt;/head&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;body&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;h1&gt;</span><span class="cp">{{</span><span class="na">.Name</span><span class="cp">}}</span><span class="s">&lt;/h1&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;p&gt;年龄：</span><span class="cp">{{</span><span class="na">.Age</span><span class="cp">}}</span><span class="s">&lt;/p&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;p&gt;注册日期：</span><span class="cp">{{</span><span class="na">.JoinedAt</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="nx">formatDate</span><span class="cp">}}</span><span class="s">&lt;/p&gt;
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="k">if</span><span class="w"> </span><span class="na">.IsPremium</span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">    &lt;span class=&#34;badge&#34;&gt;尊贵会员&lt;/span&gt;
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="k">else</span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">    &lt;span&gt;普通用户&lt;/span&gt;
</span></span></span><span class="line"><span class="cl"><span class="cp">{{</span><span class="k">end</span><span class="cp">}}</span><span class="s">
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;/body&gt;
</span></span></span><span class="line"><span class="cl"><span class="s">&lt;/html&gt;`</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">userHandler</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">ResponseWriter</span><span class="p">,</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">*</span><span class="nx">http</span><span class="p">.</span><span class="nx">Request</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">user</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">User</span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">Name</span><span class="p">:</span><span class="w">      </span><span class="s">&#34;张三&#34;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">Age</span><span class="p">:</span><span class="w">       </span><span class="mi">28</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">JoinedAt</span><span class="p">:</span><span class="w">  </span><span class="nx">time</span><span class="p">.</span><span class="nf">Now</span><span class="p">(),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">IsPremium</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">tmpl</span><span class="p">.</span><span class="nf">Execute</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">user</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nx">http</span><span class="p">.</span><span class="nf">Error</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">.</span><span class="nf">Error</span><span class="p">(),</span><span class="w"> </span><span class="nx">http</span><span class="p">.</span><span class="nx">StatusInternalServerError</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">http</span><span class="p">.</span><span class="nf">HandleFunc</span><span class="p">(</span><span class="s">&#34;/&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">userHandler</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nx">log</span><span class="p">.</span><span class="nf">Fatal</span><span class="p">(</span><span class="nx">http</span><span class="p">.</span><span class="nf">ListenAndServe</span><span class="p">(</span><span class="s">&#34;:8080&#34;</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="p">))</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>模板在包级别解析一次，请求路径只执行 <code>Execute</code>；自定义 <code>formatDate</code> 函数在 <code>Parse</code> 之前通过 <code>Funcs</code> 注册；<code>html/template</code> 自动对 <code>{{.Name}}</code> 等内容进行 HTML 转义。</p>
<h2 id="九最佳实践总结">九、最佳实践总结</h2>
<p><strong>解析时机</strong>：模板在启动时解析一次，绝不在请求路径中重复解析。使用 <code>template.Must</code> 在初始化阶段暴露解析错误。</p>
<p><strong>函数注册顺序</strong>：<code>Funcs()</code> 必须在 <code>Parse()</code> 之前调用，否则解析阶段就会失败。</p>
<p><strong>选择正确的包</strong>：生成 HTML 用 <code>html/template</code>；生成代码、配置、纯文本用 <code>text/template</code>。绝不能用 <code>text/template</code> 渲染用户可见的 HTML 页面。</p>
<p><strong>安全边界</strong>：将 <code>template.HTML</code>、<code>template.JS</code>、<code>template.CSS</code> 视为“危险类型”。它们绕过自动转义，只应用于来源完全可控的内容，严禁包裹用户输入。</p>
<p><strong>逻辑归属</strong>：模板只负责展示。复杂条件分支、数据聚合、权限判断放在 Go 代码中完成，模板接收的是“已经准备好的展示数据”。</p>
<p><strong>模板命名</strong>：嵌套模板中不同页面的内容块使用<strong>唯一名称</strong>，避免加载顺序导致同名模板被覆盖的隐蔽 bug。</p>
<p><strong>空白控制</strong>：生成结构化文本（代码、JSON、YAML）时，在 <code>range</code>、<code>if</code>、<code>end</code> 动作上使用 <code>-</code> 修剪符精确控制输出格式，避免多余换行破坏结构。</p>
<p><strong>并发写入</strong>：多 goroutine 并发执行同一模板时，为每个执行者提供独立的 <code>io.Writer</code>，避免输出交错。</p>
]]></content:encoded></item></channel></rss>