การ Reflect Attribute
Attribute reflection คืออะไร?
หัวข้อที่มีชื่อว่า “Attribute reflection คืออะไร?”Custom element เปิดเผยสถานะของตัวเองในสองรูปแบบพร้อมกัน: ในรูปแบบ JavaScript property (เช่น element.label) และในรูปแบบ HTML attribute (เช่น label="Hello") การ reflect หมายความว่าทั้งสองรูปแบบนี้ซิงก์กันตลอดเวลา — เมื่อเขียนค่าในฝั่งใดฝั่งหนึ่ง อีกฝั่งก็จะอัปเดตตามโดยอัตโนมัติ
Element <input> ดั้งเดิมทำแบบนี้กับ value, disabled, checked และอื่น ๆ คุณสามารถมอบความสะดวกสบายแบบเดียวกันให้กับ element ของตัวเองได้
คู่ getter / setter
หัวข้อที่มีชื่อว่า “คู่ getter / setter”รูปแบบ reflection คือคู่ getter และ setter ใน JavaScript ที่ส่งต่อการทำงานไปยัง getAttribute และ setAttribute:
get label() { return this.getAttribute('label') || '';}
set label(v) { this.setAttribute('label', v);}เมื่อโค้ดภายนอกอ่าน element.label getter จะคืนค่า attribute ปัจจุบัน เมื่อโค้ดกำหนด element.label = 'X' setter จะเรียก setAttribute ทำให้ HTML attribute ถูกอัปเดตทันที getAttribute('label') และ element.label จะตรงกันเสมอ
การประกาศ observed attributes
หัวข้อที่มีชื่อว่า “การประกาศ observed attributes”เพื่อให้ attributeChangedCallback ถูกเรียก — ไม่ว่า attribute จะถูกเปลี่ยนจาก HTML, DevTools หรือ JavaScript — ชื่อ attribute นั้นต้องอยู่ใน static getter ที่ชื่อ observedAttributes:
static get observedAttributes() { return ['label', 'color'];}browser อ่านรายการนี้ครั้งเดียวเมื่อ class ถูกลงทะเบียน Attribute ที่ไม่อยู่ในรายการจะถูกละเว้นโดยไม่มีการแจ้งเตือน
attributeChangedCallback
หัวข้อที่มีชื่อว่า “attributeChangedCallback”เมื่อ attribute ที่ถูกเฝ้าดูถูกเพิ่ม เปลี่ยนแปลง หรือลบออก browser จะเรียก:
attributeChangedCallback(name, oldValue, newValue) { // name — attribute ไหนที่เปลี่ยน เช่น 'label' // oldValue — สตริงค่าก่อนหน้า หรือ null เมื่อกำหนดครั้งแรก // newValue — สตริงค่าใหม่ หรือ null เมื่อถูกลบ this._render();}เนื่องจาก setter เรียก setAttribute การกำหนด element.label = 'X' จึงเรียก attributeChangedCallback เช่นเดียวกับการเขียน element.setAttribute('label', 'X') โดยตรง callback นี้คือแหล่งข้อมูลเดียวที่เชื่อถือได้สำหรับการ re-render
รูปแบบสมบูรณ์
หัวข้อที่มีชื่อว่า “รูปแบบสมบูรณ์”class MyBadge extends HTMLElement { static get observedAttributes() { return ['label', 'color']; }
get label() { return this.getAttribute('label') || ''; } set label(v) { this.setAttribute('label', v); }
get color() { return this.getAttribute('color') || 'blue'; } set color(v) { this.setAttribute('color', v); }
attributeChangedCallback(name, oldValue, newValue) { this._render(); }
connectedCallback() { this._render(); }
_render() { this.textContent = this.label; this.style.color = this.color; }}customElements.define('my-badge', MyBadge);เหตุใด reflection จึงสำคัญ
หัวข้อที่มีชื่อว่า “เหตุใด reflection จึงสำคัญ”การ reflect attribute มอบข้อดีในทางปฏิบัติให้กับ element ของคุณหลายประการ:
- มองเห็นใน DevTools — ค่า attribute ปรากฏโดยตรงในแผง Elements โดยไม่ต้องขยาย shadow DOM ใด ๆ
- CSS attribute selector — คุณสามารถกำหนด style ให้
reflect-badge[color="#7c3aed"]หรือreflect-badge[label]ได้โดยไม่ต้องใช้ class เพิ่มเติม - Server-side HTML — สถานะเริ่มต้นสามารถฝังไว้ใน markup ได้เลย ไม่จำเป็นต้องใช้ JavaScript เพื่อกำหนดค่าแรก
- ความเข้ากันได้กับ Framework — Framework อย่าง Vue, Svelte และ Angular bind กับ attribute เป็นค่าเริ่มต้น Property ที่ reflect แล้วทำให้
<my-badge label={x}>ทำงานได้ทันที
ตัวอย่างสด
หัวข้อที่มีชื่อว่า “ตัวอย่างสด”หลังจาก 1.2 วินาที property label ของ element จะถูกกำหนดค่าจาก JavaScript บรรทัด #log ยืนยันว่า element.label และ getAttribute('label') คืนค่าสตริงเดียวกัน — หลักฐานว่า setter และ attribute ซิงก์กันอย่างสมบูรณ์แบบ
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| Reflect property ไปยัง attribute | สถานะมองเห็นได้ใน DevTools ใช้เป็น CSS attribute selector ได้ และรองรับ server-rendered HTML โดยไม่ต้องพึ่ง JavaScript ตอนโหลดหน้าแรก | เสี่ยงเกิด infinite loop ถ้า setter กับ attributeChangedCallback เรียกกันไปมา และ attribute เก็บได้แค่ string เท่านั้น |
| ไม่ reflect (เก็บค่าไว้ใน private field เฉยๆ) | หลีกเลี่ยงปัญหา infinite loop ได้ทั้งหมด และรองรับข้อมูลชนิดใดก็ได้ ไม่จำกัดแค่ string | สถานะไม่ปรากฏใน markup หรือ DevTools ใช้ CSS attribute selector เลือก element ตาม state ไม่ได้ |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- ลืมประกาศชื่อ attribute ใน
observedAttributes— ถ้าไม่อยู่ในรายการนี้attributeChangedCallbackจะไม่ถูกเรียกเลย ไม่ว่าจะเปลี่ยน attribute จาก HTML, DevTools หรือ JavaScript - ทำให้ setter เรียก
setAttributeโดยไม่เช็คว่าค่าต่างจากเดิมก่อน — ทำให้เกิด infinite loop ระหว่าง setter กับattributeChangedCallbackที่เรียก setter อีกที ต้องเช็คif (this.getAttribute(name) !== v)ก่อนเขียนเสมอ - reflect property ที่เป็นข้อมูลซับซ้อน เช่น array หรือ object กลับไปเป็น attribute — attribute เป็น string เท่านั้น การพยายาม reflect object จะได้
[object Object]ที่ไร้ประโยชน์ ควร reflect เฉพาะ string, boolean หรือ number ธรรมดาเท่านั้น
💡 ตัวอย่างจากของจริง
Element
<input>ดั้งเดิมของ browser reflect property อย่างdisabledและcheckedไปยัง attribute โดยตรง ทำให้input:disabledใช้เป็น CSS selector ได้ทันทีโดยไม่ต้องเขียน JavaScript เพิ่มGitHub’s Catalyst framework ใช้ decorator
@attrเพื่อ generate getter/setter reflection pattern นี้อัตโนมัติ ลดโค้ด boilerplate ที่ต้องเขียนซ้ำในทุก custom element