Skip to content

template

เอกสารทางการ: template package - text/template - Go Packages

โดยปกติเรามักจะใช้ฟังก์ชัน fmt.Sprintf เพื่อจัดรูปแบบสตริง แต่ใช้ได้ดีเฉพาะกับสตริงขนาดเล็กเท่านั้น และต้องใช้ตัวแปรจัดรูปแบบเพื่อระบุประเภท ไม่สามารถทำพารามิเตอร์ที่มีชื่อ และไม่รองรับการจัดการสถานการณ์ที่ซับซ้อน นี่คือปัญหาที่เทมเพลตเอ็นจิ้นต้องแก้ไข เช่น ในหน้า HTML แบบคงที่ที่เชื่อมต่อกับแบ็กเอนด์โดยตรงจำเป็นต้องใช้เทมเพลตเอ็นจิ้น ในชุมชนมีไลบรารีเทมเพลตเอ็นจิ้นบุคคลที่สามที่ยอดเยี่ยมมากมาย เช่น pongo2, sprig, jet แต่บทความนี้จะอธิบายเทมเพลตเอ็นจิ้นในตัว text/template ของ Go โดยในการพัฒนาจริงมักใช้ html/template ซึ่งอิงจากเทมเพลตเอ็นจิ้นก่อนหน้าและทำการประมวลผลความปลอดภัยเกี่ยวกับ HTML มากมาย โดยทั่วไปใช้เทมเพลตเอ็นจิ้นก่อนหน้า แต่หากเกี่ยวข้องกับการประมวลผลเทมเพลต HTML แนะนำให้ใช้เทมเพลตเอ็นจิ้นหลังจะปลอดภัยกว่า

เริ่มต้นอย่างรวดเร็ว

ด้านล่างนี้เป็นตัวอย่างการใช้งานเทมเพลตเอ็นจิ้นอย่างง่าย ดังนี้

go
package main

import (
  "fmt"
  "os"
  "text/template"
)

func main() {
  tmpl := `This is the first template string, {{ .message }}`

  te, err := template.New("texTmpl").Parse(tmpl)
  if err != nil {
    fmt.Println(err)
    return
  }

  data := map[string]any{
    "message": "hello world!",
  }
  execErr := te.Execute(os.Stdout, data)
  if execErr != nil {
    fmt.Println(err)
  }
}

เอาต์พุตของโค้ดข้างต้นคือ

This is the first template string, hello world!

ในโค้ดกรณี tmpl เป็นสตริงเทมเพลต {{ .message }} ในสตริงเป็นพารามิเตอร์เทมเพลตของเทมเพลตเอ็นจิ้น ก่อนอื่นแยกวิเคราะห์สตริงเทมเพลตผ่านเมธอด *Template.Parse

go
func (t *Template) Parse(text string) (*Template, error)

หลังจากแยกวิเคราะห์สำเร็จแล้ว ใช้เมธอด *Template.Execute เพื่อใช้ข้อมูล data กับเทมเพลต สุดท้ายส่งออกไปยัง Writer ที่ส่งเข้ามาซึ่งก็คือ os.Stdout

go
func (t *Template) Execute(wr io.Writer, data any) error

ในการใช้เทมเพลตเอ็นจิ้นในอนาคต โดยพื้นฐานแล้วมีสามขั้นตอนนี้:

  1. รับเทมเพลต
  2. แยกวิเคราะห์เทมเพลต
  3. ใช้ข้อมูลกับเทมเพลต

จะเห็นว่าการใช้เทมเพลตเอ็นจิ้นนั้นง่ายมาก ส่วนที่ซับซ้อนกว่าเล็กน้อยคือไวยากรณ์เทมเพลตของเทมเพลตเอ็นจิ้น ซึ่งเป็นเนื้อหาหลักที่บทความนี้จะอธิบาย

ไวยากรณ์เทมเพลต

พารามิเตอร์

Go ใช้วงเล็บปีกกาสองคู่ {{ }} เพื่อแสดงว่าเป็นพารามิเตอร์เทมเพลตในเทมเพลต ใช้ . เพื่อแสดงอ็อบเจกต์รูท อ็อบเจกต์รูทคือ data ที่ส่งเข้ามา เหมือนกับการเข้าถึงตัวแปรสมาชิกของประเภท ผ่านสัญลักษณ์ . เชื่อมต่อชื่อตัวแปรเพื่อเข้าถึงค่าที่สอดคล้องกันในเทมเพลต เช่น

{{ .data }}

ต้องมีตัวแปรสมาชิกที่มีชื่อเดียวกัน มิฉะนั้นจะเกิดข้อผิดพลาด สำหรับ data ที่ส่งเข้ามา โดยทั่วไปเป็นโครงสร้างหรือ map หรืออาจเป็นประเภทพื้นฐาน เช่น ตัวเลข สตริง ในกรณีนี้ . ที่แสดงอ็อบเจกต์รูทคือตัวมันเอง ในวงเล็บปีกกาไม่จำเป็นต้องเข้าถึงอ็อบเจกต์รูทเพื่อรับค่าเสมอไป อาจเป็นค่าคงที่ของประเภทพื้นฐาน เช่น

{{ 1 }}
{{ 3.14 }}
{{ "jack" }}

ไม่ว่าจะเป็นประเภทใด สุดท้ายจะได้รูปแบบสตริงผ่าน fmt.Sprintf("%s", val) ดูตัวอย่างด้านล่าง

go
func main() {
  out := os.Stdout

  tmpl := "data-> {{ . }}\n"

  datas := []any{
    "hello world!",
    6379,
    3.1415926,
    []any{1, "2*2", 3.6},
    map[string]any{"data": "hello world!"},
    struct {
      Data string
    }{Data: "hello world!"},
  }

  for _, data := range datas {
    err := ExecTmpl(out, tmpl, data)
    if err != nil {
      panic(err)
    }
  }
}

func ExecTmpl(writer io.Writer, tmpl string, data any) error {
  parsedTmpl, err := template.New("template").Parse(tmpl)
  if err != nil {
    return err
  }
  return parsedTmpl.Execute(writer, data)
}

เอาต์พุตดังนี้

data-> hello world!
data-> 6379
data-> 3.1415926
data-> [1 2*2 3.6]
data-> map[data:hello world!]
data-> {hello world!}

จะเห็นว่าการเอาต์พุตเหมือนกับ直接使用 fmt.Sprintf สำหรับโครงสร้างและ map สามารถเข้าถึงค่าผ่านชื่อฟิลด์ ดังนี้

go
func main() {
  out := os.Stdout

  tmpl := "data-> {{ .Data }}\n"

  datas := []any{
    map[string]any{"Data": "hello world!"},
    struct {
      Data string
    }{Data: "hello world!"},
  }

  for _, data := range datas {
    err := ExecTmpl(out, tmpl, data)
    if err != nil {
      panic(err)
    }
  }
}

เอาต์พุตดังนี้

data-> hello world!
data-> hello world!

สำหรับสไลซ์และ map แม้ว่าจะไม่มีไวยากรณ์เฉพาะสำหรับเข้าถึงค่าดัชนีที่ระบุ แต่สามารถทำได้ผ่านการเรียกฟังก์ชัน ดังนี้

go
func main() {
  out := os.Stdout

  tmpl := "data-> {{ index . 1}}\n"

  datas := []any{
    []any{"first", "second"},
    map[int]any{1: "first"},
  }

  for _, data := range datas {
    err := ExecTmpl(out, tmpl, data)
    if err != nil {
      panic(err)
    }
  }
}

เอาต์พุต

data-> second
data-> first

หากเป็นสไลซ์หลายมิติ สามารถเข้าถึงค่าดัชนีที่สอดคล้องกันผ่านวิธีต่อไปนี้ เทียบเท่ากับ s[i][j][k]

{{ index . i j k }}

สำหรับโครงสร้างหรือ map ที่ซ้อนกัน สามารถใช้ .k1.k2.k3 เพื่อเข้าถึง เช่น

{{ .person.father.name }}

เมื่อใช้พารามิเตอร์เทมเพลต สามารถเพิ่มสัญลักษณ์ - หน้าและหลังพารามิเตอร์เพื่อลบช่องว่างหน้าและหลังพารามิเตอร์ ดูตัวอย่าง

go
func main() {
  out := os.Stdout

  tmpl := `{{ .x }} {{ - .op - }} {{ .y }}`

  datas := []any{
    map[string]any{"x": "10", "op": ">", "y": "2"},
  }

  for _, data := range datas {
    err := ExecTmpl(out, tmpl, data)
    if err != nil {
      panic(err)
    }
  }
}

โดยปกติแล้วเอาต์พุตควรเป็น 10 > 2 แต่เนื่องจากเพิ่มสัญลักษณ์ - หน้าและหลังพารามิเตอร์ op ดังนั้นช่องว่างหน้าและหลังจะถูกกำจัด ดังนั้นเอาต์พุตจริงคือ

10>2

ควรสังเกตว่าในวงเล็บปีกกา สัญลักษณ์ - ต้องห่างจากพารามิเตอร์หนึ่งช่องว่าง กล่าวคือต้องเป็นรูปแบบ {{- . -}} ในตัวอย่าง之所以เพิ่มช่องว่างพิเศษทั้งสองข้างเขียนเป็นรูปแบบ {{ - . - }} ล้วนเป็นเพราะรู้สึกว่าดูสบายตาเท่านั้น ที่จริงแล้วไม่มีข้อจำกัดทางไวยากรณ์นี้

ความคิดเห็น

ไวยากรณ์เทมเพลตรองรับความคิดเห็น ความคิดเห็นจะไม่สร้างในเทมเพลตสุดท้าย ไวยากรณ์มีดังนี้

{{/* this is a comment */}}

สัญลักษณ์ความคิดเห็น /* และ */ ต้องอยู่ติดกับวงเล็บปีกกา ระหว่างพวกมันต้องไม่มีอักขระอื่น มิฉะนั้นจะไม่สามารถแยกวิเคราะห์ได้ มีเพียงกรณีเดียวที่ข้อยกเว้น นั่นคือเมื่อกำจัดช่องว่าง

{{- /* this is a comment */ -}}

ตัวแปร

ในเทมเพลตสามารถประกาศตัวแปรได้ ใช้สัญลักษณ์ $ เพื่อแสดงว่าเป็นตัวแปร และใช้ := เพื่อกำหนดค่า เหมือนกับโค้ด Go ตัวอย่างดังนี้

{{ $name := .Name }}
{{ $val := index . 1 }}
{{ $val := index .dict key }}
// กำหนดค่าจำนวนเต็ม
{{ $numer := 1 }}
// กำหนดค่าจุดลอยตัว
{{ $float := 1.234}}
// กำหนดค่าสตริง
{{ $name := "jack" }}

ในภายหลังใช้ $ เชื่อมต่อชื่อตัวแปรเพื่อเข้าถึงค่าของตัวแปรนั้น เช่น

go
func main() {
  out := os.Stdout

  tmpl := `{{ $name := .name }} {{- $name }}`

  datas := []any{
    map[string]any{"name": "jack"},
  }

  for _, data := range datas {
    err := ExecTmpl(out, tmpl, data)
    if err != nil {
      panic(err)
    }
  }
}

เอาต์พุต

jack

ตัวแปรต้องประกาศก่อนใช้ มิฉะนั้นจะแสดงข้อผิดพลาด undefined variable และต้องใช้ภายในขอบเขตด้วย

ฟังก์ชัน

ไวยากรณ์ของเทมเพลตเองไม่มาก ส่วนใหญ่ฟังก์ชันใช้เพื่อใช้งานฟังก์ชัน รูปแบบการเรียกฟังก์ชันคือชื่อฟังก์ชันตามด้วยรายการพารามิเตอร์ ใช้ช่องว่างเป็นตัวคั่น ดังนี้

{{ funcname arg1 arg2 arg3 ... }}

เช่น ฟังก์ชัน index ที่ใช้ก่อนหน้านี้

{{ index .s 1 }}

ฟังก์ชัน eq ที่ใช้เปรียบเทียบว่าเท่ากันหรือไม่

{{ eq 1 2 }}

แต่ละ *Template มี FuncsMap สำหรับบันทึกการแมปฟังก์ชัน

go
type FuncMap map[string]any

เมื่อสร้างเทมเพลต รับการแมปฟังก์ชันเริ่มต้นจาก text/template.builtins ด้านล่างนี้เป็นฟังก์ชันในตัวทั้งหมด

ชื่อฟังก์ชันหน้าที่ตัวอย่าง
andการดำเนินการ AND{{ and true false }}
orการดำเนินการ OR{{ or true false }}
notการดำเนินการ NOT{{ not true }}
eqเท่ากันหรือไม่{{ eq 1 2 }}
neไม่เท่ากันหรือไม่{{ ne 1 2 }}
ltน้อยกว่า{{ lt 1 2 }}
leน้อยกว่าหรือเท่ากับ{{ le 1 2 }}
gtมากกว่า{{ gt 1 2 }}
geมากกว่าหรือเท่ากับ{{ ge 1 2 }}
lenส่งคืนความยาว{{ len .slice }}
indexรับองค์ประกอบดัชนีที่ระบุ{{ index . 0 }}
sliceสไลซ์ เทียบเท่ากับ s[1:2:3]{{ slice . 1 2 3 }}
htmlหลีกหนี HTML{{ html .name }}
jsหลีกหนี js{{ js .name }}
printfmt.Sprint{{ print . }}
printffmt.Sprintf{{ printf "%s" .}}
printlnfmt.Sprintln{{ println . }}
urlqueryหลีกหนี url query{{ urlquery .query }}

นอกจากนี้ยังมีฟังก์ชันในตัวพิเศษ call ใช้สำหรับเรียกฟังก์ชันที่ส่งผ่านใน data ที่ส่งเข้ามาในช่วง Execute โดยตรง เช่น เทมเพลตต่อไปนี้

{{ call .string 1024 }}

ข้อมูลที่ส่งเข้ามาดังนี้

go
map[string]any{
    "string": func(val any) string { return fmt.Sprintf("%v: 2048", val) },
}

那么在模板中就会生成

1024: 2048

นี่คือหนึ่งในวิธีกำหนดฟังก์ชันเอง แต่โดยปกติแนะนำให้ใช้เมธอด *Template.Funcs เพื่อเพิ่มฟังก์ชันเอง เพราะหลังสามารถใช้งานทั่วโลกได้ ไม่จำเป็นต้องผูกกับอ็อบเจกต์รูท

go
func (t *Template) Funcs(funcMap FuncMap) *Template

ค่าส่งกลับของฟังก์ชันที่กำหนดเองโดยทั่วไปมีสองค่า ค่าแรกคือค่าส่งกลับที่ต้องการใช้ และค่าที่สองคือ error เช่น มีฟังก์ชันที่กำหนดเองดังนี้

go
template.FuncMap{
    "add": func(val any) (string, error) { return fmt.Sprintf("%v+1", val), nil },
}

แล้วใช้ในเทมเพลตโดยตรง

{{ add 1024 }}

ผลลัพธ์คือ

1024 + 1

ไปป์ไลน์

ไปป์ไลน์นี้แตกต่างจาก chan ในเอกสารทางการเรียกว่า pipeline การดำเนินการใดๆ ที่สร้างข้อมูลเรียกว่า pipeline การดำเนินการเทมเพลตต่อไปนี้เป็นของไปป์ไลน์

{{ 1 }}
{{ eq 1 2 }}
{{ $name }}
{{ .name }}

ผู้ที่คุ้นเคยกับ linux ควรทราบตัวดำเนินการไปป์ไลน์ | เทมเพลตก็รองรับการเขียนเช่นนี้ การดำเนินการไปป์ไลน์มักปรากฏในเทมเพลต เช่น

{{ $name := 1 }}{{ $name | print | printf "%s+1=?" }}

ผลลัพธ์คือ

1+1=?

ใน with, if, range ต่อไปก็จะใช้บ่อย

with

ผ่านคำสั่ง with สามารถควบคุมขอบเขตของตัวแปรและอ็อบเจกต์รูท รูปแบบมีดังนี้

{{ with pipeline }}
  text
{{ end }}

with จะตรวจสอบค่าที่ส่งกลับจากการดำเนินการไปป์ไลน์ หากค่าเป็นว่าง เทมเพลต text ตรงกลางจะไม่สร้าง หากต้องการจัดการกรณีว่าง สามารถใช้ with else รูปแบบมีดังนี้

{{ with pipeline }}
  text1
{{ else }}
  text2
{{ end }}

หากค่าที่ส่งกลับจากการดำเนินการไปป์ไลน์เป็นว่าง ก็จะดำเนินการตรรกะของบล็อก else นี้ ตัวแปรที่ประกาศในคำสั่ง with ขอบเขตจำกัดอยู่ภายในคำสั่ง with เท่านั้น ดูตัวอย่างหนึ่ง

{{ $name := "mike" }}
{{ with $name := "jack"  }}
  {{- $name -}}
{{ end }}
{{- $name -}}

เอาต์พุตมีดังนี้ เห็นได้ชัดว่าเนื่องจากขอบเขตต่างกัน พวกมันเป็นตัวแปรคนละตัว

jackmike

ผ่านคำสั่ง with ยังสามารถเขียนอ็อบเจกต์รูทใหม่ภายในขอบเขตได้ ดังนี้

{{ with .name }}
  name: {{- .second }}-{{ .first -}}
{{ end }}
age: {{ .age }}
address: {{ .address }}

ส่งข้อมูลดังนี้

go
map[string]any{
    "name": map[string]any{
        "first":  "jack",
        "second": "bob",
    },
    "age":     1,
    "address": "usa",
}

เอาต์พุต

name:bob-jack
age: 1
address: usa

จะเห็นได้ว่าภายในคำสั่ง with อ็อบเจกต์รูท . กลายเป็น .name แล้ว

เงื่อนไข

รูปแบบคำสั่งเงื่อนไขมีดังนี้

{{ if pipeline }}
  text1
{{ else if pipeline }}
  text2
{{ else }}
  text3
{{ end }}

เหมือนกับการเขียนโค้ดทั่วไป เข้าใจง่ายมาก ดูตัวอย่างง่ายๆ สองสามตัวอย่าง

{{ if eq .lang "en" }}
  {{- .content.en -}}
{{ else if eq .lang "zh" }}
  {{- .content.zh -}}
{{ else }}
  {{- .content.fallback -}}
{{ end }}

ข้อมูลที่ส่งเข้ามา

go
map[string]any{
    "lang": "zh",
    "content": map[string]any{
        "en":       "hello, world!",
        "zh":       "你好,世界!",
        "fallback": "hello, world!",
    },
}

เทมเพลตในตัวอย่างตัดสินใจว่าจะแสดงเนื้อหาอย่างไรตาม lang ที่ส่งเข้ามา เอาต์พุต

你好,世界!

การทำซ้ำ

รูปแบบคำสั่งการทำซ้ำมีดังนี้ range รองรับ pipeline ต้องเป็นอาร์เรย์ สไลซ์ map และ channel

{{ range pipeline }}
  loop body
{{ end }}

ใช้ร่วมกับ else เมื่อความยาวเป็น 0 ก็จะดำเนินการเนื้อหาของบล็อก else

{{ range pipeline }}
  loop body
{{ else }}
  fallback
{{ end }}

นอกจากนี้ยังรองรับการดำเนินการเช่น break, continue เช่น

{{ range pipeline }}
  {{ if pipeline }}
    {{ break }}
  {{ end }}
  {{ if pipeline }}
    {{ continue }}
  {{ end }}
  loop body
{{ end }}

ดูตัวอย่างการทำซ้ำหนึ่งตัวอย่าง

{{ range $index, $val := . }}
  {{- if eq $index 0 }}
    {{- continue -}}
  {{ end -}}
  {{- $index}}: {{ $val }}
{{ end }}

ส่งข้อมูล

go
[]any{1, "2", 3.14},

เอาต์พุต

1: 2
2: 3.14

การทำซ้ำ map ก็เช่นเดียวกัน

การซ้อนกัน

ในเทมเพลตหนึ่งสามารถกำหนดเทมเพลตได้หลายเทมเพลต เช่น

{{ define "t1" }} t1 {{ end }}
{{ define "t2" }} t2 {{ end }}

เทมเพลตที่กำหนดเหล่านี้จะไม่สร้างในเทมเพลตสุดท้าย เว้นแต่จะระบุชื่อในช่วงโหลดหรือระบุด้วยตนเองผ่านคำสั่ง template

func (t *Template) ExecuteTemplate(wr io.Writer, name string, data any) error

เช่น ตัวอย่างด้านล่าง

{{ define "t1" }}
    {{- with .t1 }}
      {{- .data -}}
    {{ end -}}
{{ end }}
{{ define "t2" }}
    {{- with .t2 }}
      {{- .data -}}
    {{ end}}
{{ end -}}

ส่งข้อมูลดังนี้

go
map[string]any{
    "t1": map[string]any{"data": "template body 1"},
    "t2": map[string]any{"data": "template body 2"},
}

โค้ด

go
func main() {
  out := os.Stdout

  tmpl :=
    `{{ define "t1" }}
    {{- with .t1 }}
      {{- .data -}}
    {{ end -}}
{{ end }}
{{ define "t2" }}
    {{- with .t2 }}
      {{- .data -}}
    {{ end}}
{{ end -}}`

  datas := []any{
    map[string]any{
      "t1": map[string]any{"data": "template body 1"},
      "t2": map[string]any{"data": "template body 2"},
    },
  }

  name := "t1"

  for _, data := range datas {
    err := ExecTmpl(out, tmpl, name, data)
    if err != nil {
      panic(err)
    }
  }
}

func ExecTmpl(writer io.Writer, tmpl string, name string, data any) error {
  t := template.New("template")
  parsedTmpl, err := t.Parse(tmpl)
  if err != nil {
    return err
  }
  return parsedTmpl.ExecuteTemplate(writer, name, data)
}

เอาต์พุต

template body 1

หรือสามารถระบุเทมเพลตด้วยตนเอง

{{ define "t1" }}
    {{- with .t1 }}
      {{- .data -}}
    {{ end -}}
{{ end }}
{{ define "t2" }}
    {{- with .t2 }}
      {{- .data -}}
    {{ end}}
{{ end -}}
{{ template "t2" .}}

那么ในการแยกวิเคราะห์ไม่ว่าจะระบุชื่อเทมเพลตหรือไม่ t2 จะโหลด

การเชื่อมโยง

เทมเพลตย่อยเป็นเพียงการประกาศเทมเพลตที่มีชื่อหลายเทมเพลตภายในเทมเพลตเดียว การเชื่อมโยงคือการเชื่อมโยง *Template ที่มีชื่อหลายเทมเพลตจากภายนอก แล้วอ้างอิงเทมเพลตที่ระบุผ่านคำสั่ง template

{{ tempalte "templateName" pipeline}}

pipeline สามารถระบุอ็อบเจกต์รูทของเทมเพลตที่เชื่อมโยงตามความต้องการของตนเอง หรือส่งอ็อบเจกต์รูทของเทมเพลตปัจจุบันโดยตรง ดูตัวอย่างโค้ดหนึ่งตัวอย่าง

go
func main() {
  tmpl1 := `name: {{ .name }}`

  tmpl2 := `age: {{ .age }}`

  tmpl3 := `Person Info
{{template "t1" .}}
{{template "t2" .}}`

  t1, err := template.New("t1").Parse(tmpl1)
  if err != nil {
    panic(err)
  }

  t2, err := template.New("t2").Parse(tmpl2)
  if err != nil {
    panic(err)
  }

  t3, err := template.New("t3").Parse(tmpl3)
  if err != nil {
    panic(err)
  }

  if err := associate(t3, t1, t2); err != nil {
    panic(err)
  }

  err = t3.Execute(os.Stdout, map[string]any{
    "name": "jack",
    "age":  18,
  })
  if err != nil {
    panic(err)
  }
}

func associate(t *template.Template, ts ...*template.Template) error {
  for _, tt := range ts {
    _, err := t.AddParseTree(tt.Name(), tt.Tree)
    if err != nil {
      return err
    }
  }
  return nil
}

ในโค้ดข้างต้น t3 เชื่อมโยง t1 และ t2 ใช้เมธอด *Template.AddParseTree เพื่อเชื่อมโยง

go
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error)

ผลลัพธ์การสร้างเทมเพลตสุดท้ายคือ

Person Info
name: jack
age: 18

สล็อต

ผ่านคำสั่ง block สามารถสร้างเอฟเฟกต์คล้ายสล็อตของ vue ได้ เพื่อใช้เทมเพลตหนึ่งซ้ำ ดูตัวอย่างการใช้งานหนึ่งตัวอย่างก็จะรู้ว่าใช้อย่างไร ในเทมเพลต t1 กำหนดสล็อต

Basic Person Info
name: {{ .name }}
age: {{ .age }}
address: {{ .address }}
{{ block "slot" . }} default content body {{ end }}

คำสั่ง block สามารถเป็นเนื้อหาเริ่มต้นของสล็อต ในภายหลังเมื่อเทมเพลตอื่นใช้สล็อต จะแทนที่เนื้อหาเริ่มต้น ในเทมเพลต t2 อ้างอิงเทมเพลต t1 และใช้ define กำหนดเนื้อหาที่ฝัง

{{ template "person.txt" . }}
{{ define "slot" }}
school: {{ .school }}
{{ end }}

หลังจากเชื่อมโยงสองเทมเพลต ส่งข้อมูลดังนี้

go
map[string]any{
    "name":    "jack",
    "age":     18,
    "address": "usa",
    "company": "google",
    "school":  "mit",
}

เอาต์พุตสุดท้ายคือ

Basic Person Info
name: jack
age: 18
address: usa

school: mit

ไฟล์เทมเพลต

ในกรณีไวยากรณ์เทมเพลต ใช้ค่าคงที่สตริงเป็นเทมเพลต ในสถานการณ์การใช้งานจริง ส่วนใหญ่มักวางเทมเพลตในไฟล์

go
func ParseFS(fsys fs.FS, patterns ...string) (*Template, error)

เช่น template.ParseFs โหลดเทมเพลตที่ตรงกับ pattern จากระบบไฟล์ที่ระบุ ตัวอย่างด้านล่างใช้ embed.FS เป็นระบบไฟล์ เตรียมสามไฟล์

txt
# person.txt
Basic Person Info
name: {{ .name }}
age: {{ .age }}
address: {{ .address }}
{{ block "slot" . }} {{ end }}

# student.txt
{{ template "person.txt" . }}
{{ define "slot" }}
school: {{ .school }}
{{ end }}

# employee.txt
{{ template "person.txt" . }}
{{ define "slot" }}
company: {{ .company }}
{{ end }}

โค้ด

go
import (
  "embed"
  "os"
  "text/template"
)

//go:embed *.txt
var fs embed.FS

func main() {
  data := map[string]any{
    "name":    "jack",
    "age":     18,
    "address": "usa",
    "company": "google",
    "school":  "mit",
  }

  t1, err := template.ParseFS(fs, "person.txt", "student.txt")
  if err != nil {
    panic(err)
  }

  t1.Execute(os.Stdout, data)

  t2, err := template.ParseFS(fs, "person.txt", "employee.txt")
  if err != nil {
    panic(err)
  }
  t2.Execute(os.Stdout, data)
}

เอาต์พุตคือ

Basic Person Info
name: jack
age: 18
address: usa

school: mit
Basic Person Info
name: jack
age: 18
address: usa

company: google

นี่เป็นตัวอย่างการใช้งานไฟล์เทมเพลตอย่างง่าย person.txt เป็นไฟล์สล็อต อีกสองไฟล์ใช้เนื้อหาซ้ำและฝังเนื้อหาใหม่ที่กำหนดเอง นอกจากนี้ยังสามารถใช้ฟังก์ชันสองฟังก์ชันต่อไปนี้

go
func ParseGlob(pattern string) (*Template, error)

func ParseFiles(filenames ...string) (*Template, error)

ParseGlob ใช้การจับคู่ไวด์การ์ด ParseFiles ใช้ชื่อไฟล์ ทั้งสองใช้ระบบไฟล์ท้องถิ่น หากใช้สำหรับแสดงไฟล์ html บนฟรอนต์เอนด์ แนะนำให้ใช้แพ็กเกจ html/template ซึ่งให้ API เหมือนกับ text/template แต่ทำการประมวลผลความปลอดภัยสำหรับ html, css, js

Golang by www.golangdev.cn edit