Go 18.0 专业五


01. Template

<h1>1. Template</h1> <p>html/template包实现了数据驱动的模板,用于生成可对抗代码注入的安全HTML输出。它提供了和text/template包相同的接口,Go语言中输出HTML的场景都应使用text/template包。</p> <h2>1.1. 模板</h2> <p>在基于MVC的Web架构中,我们通常需要在后端渲染一些数据到HTML文件中,从而实现动态的网页效果。</p> <h3>1.1.1. 模板示例</h3> <p>通过将模板应用于一个数据结构(即该数据结构作为模板的参数)来执行,来获得输出。模板中的注释引用数据接口的元素(一般如结构体的字段或者字典的键)来控制执行过程和获取需要呈现的值。模板执行时会遍历结构并将指针表示为’.‘(称之为”dot”)指向运行过程中数据结构的当前位置的值。</p> <p>用作模板的输入文本必须是utf-8编码的文本。”Action”—数据运算和控制单位—由”“界定;在Action之外的所有文本都不做修改的拷贝到输出中。Action内部不能有换行,但注释可以有换行。</p> <p>HTML文件代码如下:</p> <pre><code>&lt;!DOCTYPE html&gt; &lt;html lang="en"&gt; &lt;head&gt; &lt;meta charset="UTF-8"&gt; &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt; &lt;meta http-equiv="X-UA-Compatible" content="ie=edge"&gt; &lt;title&gt;Hello&lt;/title&gt; &lt;/head&gt; &lt;body&gt; &lt;p&gt;Hello {{.}}&lt;/p&gt; &lt;/body&gt; &lt;/html&gt;</code></pre> <p>我们的HTTP server端代码如下:</p> <pre><code>// main.go func sayHello(w http.ResponseWriter, r *http.Request) { // 解析指定文件生成模板对象 tmpl, err := template.ParseFiles("./hello.html") if err != nil { fmt.Println("create template failed, err:", err) return } // 利用给定数据渲染模板,并将结果写入w tmpl.Execute(w, "5lmh.com") } func main() { http.HandleFunc("/", sayHello) err := http.ListenAndServe(":9090", nil) if err != nil { fmt.Println("HTTP server failed,err:", err) return } }</code></pre> <h3>1.1.2. 模板语法</h3> <h4><code>{{.}}</code></h4> <p>模板语法都包含在<code>{{和}}</code>中间,其中<code>{{.}}</code>中的点表示当前对象。</p> <p>当我们传入一个结构体对象时,我们可以根据<code>.</code>来访问结构体的对应字段。例如:</p> <pre><code>// main.go type UserInfo struct { Name string Gender string Age int } func sayHello(w http.ResponseWriter, r *http.Request) { // 解析指定文件生成模板对象 tmpl, err := template.ParseFiles("./hello.html") if err != nil { fmt.Println("create template failed, err:", err) return } // 利用给定数据渲染模板,并将结果写入w user := UserInfo{ Name: "枯藤", Gender: "男", Age: 18, } tmpl.Execute(w, user) }</code></pre> <p>HTML文件代码如下:</p> <pre><code>&lt;!DOCTYPE html&gt; &lt;html lang="en"&gt; &lt;head&gt; &lt;meta charset="UTF-8"&gt; &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt; &lt;meta http-equiv="X-UA-Compatible" content="ie=edge"&gt; &lt;title&gt;Hello&lt;/title&gt; &lt;/head&gt; &lt;body&gt; &lt;p&gt;Hello {{.Name}}&lt;/p&gt; &lt;p&gt;性别:{{.Gender}}&lt;/p&gt; &lt;p&gt;年龄:{{.Name}}&lt;/p&gt; &lt;/body&gt; &lt;/html&gt;</code></pre> <p>同理,当我们传入的变量是map时,也可以在模板文件中通过.根据key来取值。</p> <h4>注释</h4> <pre><code> {{/* a comment */}} 注释,执行时会忽略。可以多行。注释不能嵌套,并且必须紧贴分界符始止。</code></pre> <h4>pipeline</h4> <p>pipeline是指产生数据的操作。比如<code>{{.}}</code>、<code>{{.Name}}</code>等。Go的模板语法中支持使用管道符号|链接多个命令,用法和unix下的管道类似:|前面的命令会将运算结果(或返回值)传递给后一个命令的最后一个位置。</p> <p>注意 : 并不是只有使用了|才是pipeline。Go的模板语法中,pipeline的概念是传递数据,只要能产生数据的,都是pipeline。</p> <h4>变量</h4> <p>Action里可以初始化一个变量来捕获管道的执行结果。初始化语法如下:</p> <pre><code> $variable := pipeline</code></pre> <p>其中$variable是变量的名字。声明变量的action不会产生任何输出。</p> <h3>1.1.3. 条件判断</h3> <p>Go模板语法中的条件判断有以下几种:</p> <pre><code>{{if pipeline}} T1 {{end}} {{if pipeline}} T1 {{else}} T0 {{end}} {{if pipeline}} T1 {{else if pipeline}} T0 {{end}}</code></pre> <h4>range</h4> <p>Go的模板语法中使用range关键字进行遍历,有以下两种写法,其中pipeline的值必须是数组、切片、字典或者通道。</p> <pre><code>{{range pipeline}} T1 {{end}} 如果pipeline的值其长度为0,不会有任何输出 {{range pipeline}} T1 {{else}} T0 {{end}} 如果pipeline的值其长度为0,则会执行T0。</code></pre> <h4>with</h4> <pre><code>{{with pipeline}} T1 {{end}} 如果pipeline为empty不产生输出,否则将dot设为pipeline的值并执行T1。不修改外面的dot。 {{with pipeline}} T1 {{else}} T0 {{end}} 如果pipeline为empty,不改变dot并执行T0,否则dot设为pipeline的值并执行T1。</code></pre> <h3>1.1.4. 预定义函数</h3> <p>执行模板时,函数从两个函数字典中查找:首先是模板函数字典,然后是全局函数字典。一般不在模板内定义函数,而是使用Funcs方法添加函数到模板里。</p> <p>预定义的全局函数如下:</p> <pre><code>and 函数返回它的第一个empty参数或者最后一个参数; 就是说"and x y"等价于"if x then y else x";所有参数都会执行; or 返回第一个非empty参数或者最后一个参数; 亦即"or x y"等价于"if x then x else y";所有参数都会执行; not 返回它的单个参数的布尔值的否定 len 返回它的参数的整数类型长度 index 执行结果为第一个参数以剩下的参数为索引/键指向的值; 如"index x 1 2 3"返回x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典。 print 即fmt.Sprint printf 即fmt.Sprintf println 即fmt.Sprintln html 返回其参数文本表示的HTML逸码等价表示。 urlquery 返回其参数文本表示的可嵌入URL查询的逸码等价表示。 js 返回其参数文本表示的JavaScript逸码等价表示。 call 执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函数的参数; 如"call .X.Y 1 2"等价于go语言里的dot.X.Y(1, 2); 其中Y是函数类型的字段或者字典的值,或者其他类似情况; call的第一个参数的执行结果必须是函数类型的值(和预定义函数如print明显不同); 该函数类型值必须有1到2个返回值,如果有2个则后一个必须是error接口类型; 如果有2个返回值的方法返回的error非nil,模板执行会中断并返回给调用模板执行者该错误;</code></pre> <h3>1.1.5. 比较函数</h3> <p>布尔函数会将任何类型的零值视为假,其余视为真。</p> <p>下面是定义为函数的二元比较运算的集合:</p> <pre><code> eq 如果arg1 == arg2则返回真 ne 如果arg1 != arg2则返回真 lt 如果arg1 &lt; arg2则返回真 le 如果arg1 &lt;= arg2则返回真 gt 如果arg1 &gt; arg2则返回真 ge 如果arg1 &gt;= arg2则返回真</code></pre> <p>为了简化多参数相等检测,eq(只有eq)可以接受2个或更多个参数,它会将第一个参数和其余参数依次比较,返回下式的结果:</p> <pre><code> {{eq arg1 arg2 arg3}}</code></pre> <p>比较函数只适用于基本类型(或重定义的基本类型,如”type Celsius float32”)。但是,整数和浮点数不能互相比较。</p> <h3>1.1.6. 自定义函数</h3> <p>Go的模板支持自定义函数。</p> <pre><code>func sayHello(w http.ResponseWriter, r *http.Request) { htmlByte, err := ioutil.ReadFile("./hello.html") if err != nil { fmt.Println("read html failed, err:", err) return } // 自定义一个夸人的模板函数 kua := func(arg string) (string, error) { return arg + "真帅", nil } // 采用链式操作在Parse之前调用Funcs添加自定义的kua函数 tmpl, err := template.New("hello").Funcs(template.FuncMap{"kua": kua}).Parse(string(htmlByte)) if err != nil { fmt.Println("create template failed, err:", err) return } user := UserInfo{ Name: "枯藤", Gender: "男", Age: 18, } // 使用user渲染模板,并将结果写入w tmpl.Execute(w, user) }</code></pre> <p>我们可以在模板文件hello.html中使用我们自定义的kua函数了。</p> <pre><code> {{kua .Name}}</code></pre> <h3>1.1.7. 嵌套template</h3> <p>我们可以在template中嵌套其他的template。这个template可以是单独的文件,也可以是通过define定义的template。</p> <p>举个例子: t.html文件内容如下:</p> <pre><code>&lt;!DOCTYPE html&gt; &lt;html lang="en"&gt; &lt;head&gt; &lt;meta charset="UTF-8"&gt; &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt; &lt;meta http-equiv="X-UA-Compatible" content="ie=edge"&gt; &lt;title&gt;tmpl test&lt;/title&gt; &lt;/head&gt; &lt;body&gt; &lt;h1&gt;测试嵌套template语法&lt;/h1&gt; &lt;hr&gt; {{template "ul.html"}} &lt;hr&gt; {{template "ol.html"}} &lt;/body&gt; &lt;/html&gt; {{ define "ol.html"}} &lt;h1&gt;这是ol.html&lt;/h1&gt; &lt;ol&gt; &lt;li&gt;吃饭&lt;/li&gt; &lt;li&gt;睡觉&lt;/li&gt; &lt;li&gt;打豆豆&lt;/li&gt; &lt;/ol&gt; {{end}}</code></pre> <p>ul.html文件内容如下:</p> <pre><code>&lt;ul&gt; &lt;li&gt;注释&lt;/li&gt; &lt;li&gt;日志&lt;/li&gt; &lt;li&gt;测试&lt;/li&gt; &lt;/ul&gt;</code></pre> <p>我们注册一个templDemo路由处理函数.</p> <pre><code>http.HandleFunc("/tmpl", tmplDemo)</code></pre> <p>tmplDemo函数的具体内容如下:</p> <pre><code>func tmplDemo(w http.ResponseWriter, r *http.Request) { tmpl, err := template.ParseFiles("./t.html", "./ul.html") if err != nil { fmt.Println("create template failed, err:", err) return } user := UserInfo{ Name: "枯藤", Gender: "男", Age: 18, } tmpl.Execute(w, user) }</code></pre>

页面列表

ITEM_HTML