การแนบ Shadow Root
การเรียก attachShadow
หัวข้อที่มีชื่อว่า “การเรียก attachShadow”Shadow tree ไม่ได้เกิดขึ้นเองเมื่อ custom element ถูกสร้าง — คุณต้องขอ shadow tree อย่างชัดเจนก่อน ถึงจะได้ DOM ที่แยกขอบเขตออกจากหน้าเว็บภายนอก
ในการสร้าง shadow tree ให้เรียก this.attachShadow() บน host element และส่ง mode option เข้าไป method นี้จะคืนค่า shadow root ใหม่และเก็บไว้เป็น this.shadowRoot ด้วย
class ScopedCard extends HTMLElement { connectedCallback() { this.attachShadow({ mode: 'open' }); }}คุณสามารถเรียก attachShadow ได้ใน constructor หรือใน connectedCallback การใช้ constructor เหมาะเมื่อต้องการให้ shadow root พร้อมใช้งานทันที ส่วน connectedCallback ก็ใช้ได้ดีเมื่อต้องการเข้าถึง attribute ที่กำหนดใน HTML ด้วย
การเข้าถึง shadow root
หัวข้อที่มีชื่อว่า “การเข้าถึง shadow root”หลังจากเรียก attachShadow แล้ว shadow root จะเข้าถึงได้ 2 วิธี:
// คืนค่าโดยตรงจากการเรียกvar root = this.attachShadow({ mode: 'open' });
// เก็บไว้เป็น property บน element ด้วยvar root = this.shadowRoot;ทั้งสอง reference ชี้ไปยัง ShadowRoot object เดียวกัน ShadowRoot ทำงานเหมือน DocumentFragment — คุณสามารถเรียก querySelector, getElementById และกำหนด innerHTML บน object นี้ได้
การแสดงเนื้อหาด้วย innerHTML
หัวข้อที่มีชื่อว่า “การแสดงเนื้อหาด้วย innerHTML”วิธีที่ง่ายที่สุดในการเติมเนื้อหาใน shadow tree คือการกำหนด this.shadowRoot.innerHTML วาง <style> tag ไว้ก่อน ตามด้วย markup:
this.shadowRoot.innerHTML = '<style>' + ' .card { border: 2px solid #6366f1; border-radius: 8px; padding: 1rem; background: #eef2ff; }' + ' h2 { color: #4f46e5; margin: 0 0 0.5rem; }' + '</style>' + '<div class="card"><h2>Shadow Card</h2><p>Styles scoped inside shadow root</p></div>';สไตล์จะใช้ได้เฉพาะภายใน shadow tree เท่านั้น element .card ที่อยู่ใน light DOM จะไม่ได้รับผลกระทบ
ตัวอย่าง class เต็ม
หัวข้อที่มีชื่อว่า “ตัวอย่าง class เต็ม”class ScopedCard extends HTMLElement { connectedCallback() { this.attachShadow({ mode: 'open' }); this.shadowRoot.innerHTML = '<style>' + ' .card { border: 2px solid #6366f1; border-radius: 8px; padding: 1rem; background: #eef2ff; }' + ' h2 { color: #4f46e5; margin: 0 0 0.5rem; }' + ' p { color: #3730a3; margin: 0; }' + '</style>' + '<div class="card"><h2>Shadow Card</h2><p>Styles scoped inside shadow root</p></div>'; }}customElements.define('scoped-card', ScopedCard);ตัวอย่างที่รันได้
หัวข้อที่มีชื่อว่า “ตัวอย่างที่รันได้”ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
เรียก attachShadow ใน constructor | shadow root พร้อมใช้งานทันทีตั้งแต่สร้าง instance | ยังอ่าน attribute จาก HTML ไม่ได้ในจังหวะนี้ |
เรียก attachShadow ใน connectedCallback | อ่าน attribute ที่ประกาศใน HTML ได้ตั้งแต่ครั้งแรกที่ element เข้า DOM | ต้องเช็คก่อนว่ามี shadow root อยู่แล้วหรือยัง เผื่อ element ถูก reconnect |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- เรียก
attachShadowซ้ำ — ถ้า element ถูกย้ายตำแหน่งใน DOM (disconnectedCallbackตามด้วยconnectedCallbackอีกครั้ง) และโค้ดเรียกattachShadowใหม่ทุกครั้ง จะได้ error เพราะ shadow root แนบได้แค่ครั้งเดียวต่อ element ควรเช็คthis.shadowRootก่อนเรียกซ้ำ - ตั้งค่า
innerHTMLก่อนเรียกattachShadow—this.shadowRootจะยังเป็นnullจนกว่าจะเรียกattachShadowแล้ว ต้องเรียกก่อนเสมอ - ต่อ string HTML ด้วยมือจาก user input — การ concatenate
innerHTMLแบบในตัวอย่างสะดวกสำหรับ demo แต่ถ้าข้อมูลมาจากภายนอกควรใช้textContentหรือ sanitize ก่อน ไม่งั้นเสี่ยง XSS
💡 ตัวอย่างจากของจริง
YouTube — ใช้
attachShadowในการสร้าง video player component (<yt-icon-button>และองค์ประกอบอื่นๆ) เพื่อแยกโครงสร้างภายในออกจากหน้าเว็บหลักSalesforce Lightning (LWC) — เรียก
attachShadowเป็นส่วนหนึ่งของ lifecycle ของทุก component เพื่อ encapsulate UI ระดับ enterprise ที่มีหลายทีมพัฒนาพร้อมกัน