Template Element
template element คืออะไร
หัวข้อที่มีชื่อว่า “template element คืออะไร”เวลาต้อง stamp DOM structure เดิมซ้ำ ๆ หลายครั้ง (เช่น การ์ดในลิสต์) การสร้าง element ด้วย innerHTML string ทุกครั้งทั้งช้าและเสี่ยง XSS — <template> แก้ปัญหานี้โดยให้คุณนิยาม markup ไว้ล่วงหน้าแบบ parse ครั้งเดียวแล้ว clone ได้ไม่จำกัด
<template> element เก็บ markup ของ HTML ที่จะไม่ถูก render เมื่อหน้าเว็บโหลด ต่างจาก <div> ที่ซ่อนไว้ เนื้อหาของ template ไม่มี side-effect เลย script ภายในไม่ทำงาน รูปไม่โหลด และ style ไม่ถูกนำมาใช้ property content ของตัวเองเปิดเผย markup ออกมาในรูปของ DocumentFragment ที่เป็น container ที่เบา บรรจุ node ที่ถูก parse แล้วไว้ล่วงหน้า และอยู่นอก document tree จริงจนกว่าคุณจะแทรกเข้าไปอย่างชัดเจน
การเข้าถึงและการ clone
หัวข้อที่มีชื่อว่า “การเข้าถึงและการ clone”ใช้ document.querySelector('template').content (หรือ getElementById) เพื่อดึง DocumentFragment ออกมา เรียก content.cloneNode(true) เพื่อทำสำเนาแบบลึก (deep copy) ที่รวม node ลูกหลานทั้งหมด พร้อมแทรกได้ทุกที่ใน document
<template id="card-tpl"> <div class="card"><p class="msg"></p></div></template>const tpl = document.getElementById('card-tpl');const clone = tpl.content.cloneNode(true);clone.querySelector('.msg').textContent = 'Hello from template!';document.body.appendChild(clone);ทุกครั้งที่เรียก cloneNode(true) จะได้สำเนาที่เป็นอิสระต่อกัน การเปลี่ยนแปลง clone ตัวหนึ่งจะไม่กระทบ DocumentFragment ต้นฉบับหรือ clone ตัวอื่น
ทำไม template ถึงมีประสิทธิภาพ
หัวข้อที่มีชื่อว่า “ทำไม template ถึงมีประสิทธิภาพ”เนื่องจากเนื้อหาของ template อยู่ใน DocumentFragment ที่เป็น inert browser จึงไม่ทำ layout ไม่ทำ styling และไม่รัน script ให้เลยจนกว่าคุณจะ clone แล้วแทรกเข้าไป ต้นทุนการ parse จ่ายเพียงครั้งเดียวตอนหน้าเว็บโหลด และทุก ๆ cloneNode(true) หลังจากนั้นเป็นการคัดลอกโครงสร้างที่รวดเร็ว ทำให้ <template> เหมาะอย่างยิ่งสำหรับการ stamp instance ของ markup เดียวกันจำนวนมาก เช่น list item, card หรือ custom element ที่ใช้ซ้ำ
เดโมที่รันได้
หัวข้อที่มีชื่อว่า “เดโมที่รันได้”custom element my-card ด้านล่างใช้ <template> ที่นิยามไว้ใน light DOM ภายใน connectedCallback จะ attach shadow root และ append clone ของเนื้อหา template เข้าไป ทั้งสอง instance ใช้ template ที่ถูก parse แล้วร่วมกัน เป็นการสาธิตแพทเทิร์น “parse once, stamp many”
<template> อยู่ใน light DOM และถูกเข้าถึงผ่าน ID แต่ละ instance ของ my-card จะได้ shadow root ของตัวเอง ที่ถูกเติมด้วย clone อิสระของเนื้อหา template
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
<template> + cloneNode(true) | parse markup เพียงครั้งเดียว แล้ว clone ได้เร็วและปลอดภัยจาก XSS เพราะไม่ผ่าน string parsing ซ้ำ | ต้องเขียนโค้ดเพิ่มเพื่อดึงและ clone content ไม่ได้สั้นเท่าการต่อ string ตรง ๆ |
สร้าง markup ด้วย innerHTML string ซ้ำ ๆ | เขียนง่าย เห็นผลเร็วสำหรับ prototype | ช้ากว่าเพราะต้อง parse HTML ใหม่ทุกครั้ง และเสี่ยง XSS ถ้ามีข้อมูลจากผู้ใช้งานปนอยู่ใน string |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- ลืมเรียก
.cloneNode(true)ก่อนใช้งาน content ของ template —template.contentเป็นDocumentFragmentที่ใช้ร่วมกันเพียงชุดเดียว การแทรกตรง ๆ โดยไม่ clone จะทำให้ fragment ต้นฉบับถูกย้าย (moved) ออกไป ทำให้ instance ถัดไปได้ fragment ว่างเปล่า - เรียก
cloneNode()โดยไม่ใส่true— จะได้ shallow clone ที่ไม่มี node ลูกหลานเลย ต้องระบุtrueเสมอเพื่อทำ deep copy - คาดหวังว่า script หรือ style ภายใน
<template>จะทำงานทันทีตอนหน้าเว็บโหลด — เนื้อหาของ template เป็น inert จนกว่าจะถูก clone และแทรกเข้า document จริง script จะไม่รันและ style จะไม่ถูกนำไปใช้จนกว่าจะถึงตอนนั้น
💡 ตัวอย่างจากของจริง
Lit ใช้แนวคิดคล้าย
<template>ภายใน internal เพื่อ cache parsed markup แล้ว clone ซ้ำเมื่อ re-render คอมโพเนนต์ ลดต้นทุนการ parse ซ้ำShoelace และ web component library อื่น ๆ มักใช้
<template>ร่วมกับcloneNode(true)ในคอมโพเนนต์ที่ต้อง stamp โครงสร้างเดิมซ้ำหลายครั้ง เช่น list item หรือ menu option