Kodokon kodokon.com

Web Components: custom elements, shadow DOM และ slots

เชี่ยวชาญวงจรชีวิตของ custom element การห่อหุ้มด้วย shadow DOM และการฉายเนื้อหาผ่าน slots

12 นาที · 3 คำถาม

เปิดบทเรียนนี้ใน Kodokon

Custom elements ขยายคลังคำศัพท์ของ HTML: คลาสใด ๆ ที่สืบทอดจาก HTMLElement สามารถลงทะเบียนใน CustomElementRegistry ได้ ตราบใดที่ชื่อของมันมีเครื่องหมายขีด (user-badge ไม่ใช่ userbadge) จุดละเอียด: หากแท็กปรากฏในเอกสารก่อนที่จะเรียก customElements.define มันจะถูกสร้างเป็นองค์ประกอบที่ไม่รู้จัก (unknown element) ก่อน แล้วจึงถูก upgrade ในที่: constructor จะถูกเรียกในภายหลังบนโหนดที่มีอยู่ในต้นไม้แล้ว

JAVASCRIPT
class UserBadge extends HTMLElement {
  static observedAttributes = ["name"];
  connectedCallback() {
    this.render();
  }
  attributeChangedCallback() {
    this.render();
  }
  render() {
    this.textContent = this.getAttribute("name");
  }
}
customElements.define("user-badge", UserBadge);
custom element ขั้นต่ำที่มีวงจรชีวิตครบถ้วน

วงจรชีวิตมีกับดักอยู่บ้าง: attributeChangedCallback จะทำงานเฉพาะกับแอตทริบิวต์ที่ระบุไว้ใน observedAttributes เท่านั้น และระหว่างการ upgrade มันจะถูกเรียกสำหรับทุกแอตทริบิวต์ที่มีอยู่แล้ว ก่อน connectedCallback ข้อกำหนดห้าม constructor ตรวจสอบแอตทริบิวต์และลูก ๆ หรือเพิ่มสิ่งใด: ในขณะที่มันทำงาน (ผ่าน createElement หรือการโคลน) องค์ประกอบอาจว่างเปล่าโดยสิ้นเชิง

HTML
<template id="card-tpl">
  <style>
    :host { display: block; padding: 1rem; }
    ::slotted(h2) { margin: 0; }
  </style>
  <slot name="title"></slot>
  <slot></slot>
</template>
template ที่เฉื่อย: ถูก parse แต่ไม่ถูกรันหรือเรนเดอร์

attachShadow สร้างต้นไม้ย่อยที่ถูกห่อหุ้ม: สไตล์ไม่รั่วออกจากมัน ตัวเลือกของหน้าเว็บเข้าถึงมันไม่ได้ - มีเพียงคุณสมบัติ CSS ที่สืบทอดได้ (color, font) และ custom properties เท่านั้นที่ข้ามขอบเขตได้ เนื้อหาของ <template> อาศัยอยู่ใน DocumentFragment ที่เข้าถึงได้ผ่าน .content: สคริปต์ของมันไม่ทำงานและรูปภาพของมันไม่โหลดจนกว่าคุณจะโคลนมันเข้าไปในเอกสาร โหนดที่วางไว้ใน light DOM จะถูก ฉาย (projected) เข้าไปในองค์ประกอบ <slot>: พวกมันยังคงอยู่ใน light DOM ทางกายภาพ และเหตุการณ์ slotchange จะแจ้งให้คุณทราบถึงการเปลี่ยนแปลงใด ๆ ในการกำหนด (assignment)

JAVASCRIPT
class InfoCard extends HTMLElement {
  constructor() {
    super();
    const tpl = document.getElementById("card-tpl");
    this.attachShadow({ mode: "open" })
      .appendChild(tpl.content.cloneNode(true));
  }
}
customElements.define("info-card", InfoCard);
การโคลน template เข้าไปใน open shadow root

ส่วนขยายสองอย่างสมควรได้รับความสนใจจากคุณ Form-associated custom elements: ด้วย static formAssociated = true และ attachInternals() คอมโพเนนต์ของคุณจะมีส่วนร่วมในฟอร์มอย่างเต็มที่ (ค่าที่ส่ง การตรวจสอบความถูกต้องแบบเนทีฟ สถานะ :invalid) และ declarative shadow DOM: <template shadowrootmode=open> จะแนบ shadow root ในเวลา parse โดยไม่ต้องใช้ JavaScript ซึ่งทำให้ web components เข้ากันได้กับการเรนเดอร์ฝั่งเซิร์ฟเวอร์

ทดสอบความรู้

ตรวจสอบว่าคุณจำประเด็นสำคัญของบทเรียนนี้ได้ครบถ้วน

  1. ทำไม constructor ของ custom element ต้องไม่อ่านแอตทริบิวต์และไม่สร้างลูก ๆ?
    • เพราะแอตทริบิวต์ถูกแช่แข็งเป็นอ่านอย่างเดียวก่อน connectedCallback
    • เพราะ shadow DOM ซ่อน light DOM ระหว่างการสร้าง
    • เพราะในขณะนั้น (createElement การโคลน) องค์ประกอบอาจว่างเปล่า; ข้อกำหนดต้องการ constructor ที่ไม่มีผลกระทบต่อเนื้อหา
  2. ด้วย attachShadow({ mode: 'closed' }) แล้ว element.shadowRoot ส่งคืนอะไรจากภายนอก?
    • null: มีเพียงการอ้างอิงที่ส่งคืนจาก attachShadow เท่านั้นที่ให้เข้าถึง shadow root ได้
    • shadow root แต่อ่านอย่างเดียว
    • DocumentFragment ที่ว่างเปล่า
    • เกิด DOMException ขึ้น
  3. โหนดที่ถูกฉายเข้าไปใน <slot> อาศัยอยู่ที่ไหนทางกายภาพ?
    • พวกมันถูกย้ายเข้าไปใน shadow DOM ในเวลาเรนเดอร์
    • พวกมันอยู่ใน light DOM; slot เพียงแค่แสดงพวกมันที่ตำแหน่งของมันในต้นไม้ที่ถูกแบนราบ (flattened tree)
    • พวกมันถูกโคลน: ต้นฉบับอยู่ใน light DOM สำเนาอยู่ใน shadow DOM