Constructor และ connectedCallback
constructor
หัวข้อที่มีชื่อว่า “constructor”constructor คือจุดที่คุณตั้งค่า internal state เริ่มต้นของ element เช่น กำหนดค่า property หรือสร้าง Shadow DOM ข้อกำหนดที่สำคัญมีดังนี้:
- ต้องเรียก
super()เป็น statement แรกเสมอ - ห้าม อ่านหรือเขียน attribute ของ element ใน constructor
- ห้าม แตะ children หรือ parent node ของ element ใน constructor
- ห้าม render เนื้อหาลงใน DOM ใน constructor
เหตุผลคือเมื่อ constructor ถูกเรียก element ยังไม่ได้อยู่ใน document และ attribute ยังไม่ได้ถูกกำหนดค่า
class MyElement extends HTMLElement { constructor() { super(); // ต้องมาก่อนเสมอ this._count = 0; // ตั้งค่า internal state ได้ this._data = null; // ห้ามทำ: this.getAttribute('foo') หรือ this.innerHTML = '...' }}connectedCallback
หัวข้อที่มีชื่อว่า “connectedCallback”connectedCallback จะถูกเรียกทุกครั้งที่ element ถูกแทรกเข้าไปใน document ที่เชื่อมต่ออยู่ นี่คือจุดที่ถูกต้องและปลอดภัยสำหรับ:
- การ render เนื้อหา HTML ของ element
- การอ่าน attribute เพื่อกำหนดพฤติกรรม
- การลงทะเบียน event listener
- การเริ่มต้น timer หรือ fetch ข้อมูล
สิ่งที่ต้องระวังคือ connectedCallback จะถูกเรียกซ้ำทุกครั้งที่ element ถูกแทรกเข้าไปใหม่ ถ้าย้าย element ไปยัง parent อื่นด้วย appendChild callback นี้จะถูกเรียกอีกครั้ง
class MyElement extends HTMLElement { connectedCallback() { // อ่าน attribute ได้อย่างปลอดภัย var label = this.getAttribute('label') || 'default'; this.textContent = label; // เพิ่ม listener ได้ this.addEventListener('click', this._handleClick); }}ตัวอย่างที่รันได้จริง
หัวข้อที่มีชื่อว่า “ตัวอย่างที่รันได้จริง”สังเกตว่า constructor ถูกเรียกก่อน แต่ไม่ได้แตะ DOM ส่วน connectedCallback จะนับ _count ขึ้นทุกครั้งที่ element ถูกแทรกเข้าไปใหม่
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
ตั้งค่าใน constructor | ทำงานเร็วที่สุด ก่อน element เข้า DOM เหมาะกับ internal state ล้วนๆ | เข้าถึง attribute, children, parent ไม่ได้เลย เพราะ element ยังไม่ถูกแทรกเข้า document |
ตั้งค่าใน connectedCallback | เข้าถึง attribute, light DOM children, parent ได้ครบ และ render ได้อย่างปลอดภัย | ถูกเรียกซ้ำได้หลายครั้งตลอดอายุ element ต้องเขียนโค้ดให้ทนต่อการเรียกซ้ำ (idempotent) |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- อ่าน attribute ใน
constructor— attribute ยังไม่ถูกกำหนดค่าตอนที่ constructor ทำงาน ให้ย้าย logic การอ่าน attribute ไปไว้ในconnectedCallbackแทน - แตะ children หรือ parent ใน
constructor— light DOM children ยังไม่ถูก parse ครบตอนนั้น การเข้าถึงthis.childrenหรือthis.parentNodeใน constructor อาจได้ค่าที่ไม่ครบหรือnull - สมมติว่า
connectedCallbackเรียกแค่ครั้งเดียว — ถ้าย้าย element ด้วยappendChildไปยัง parent ใหม่ browser จะเรียกdisconnectedCallbackแล้วตามด้วยconnectedCallbackอีกครั้ง ควรเขียนให้ re-run ได้โดยไม่พังหรือซ้ำซ้อน (เช่น เช็คก่อนเพิ่ม event listener ซ้ำ)
💡 ตัวอย่างจากของจริง
YouTube — custom elements ของ YouTube ต้องรองรับกรณีที่ script โหลดแบบ async ทำให้ element อาจถูก construct และ connect ก่อนที่ dependency บางตัวจะพร้อม การแยก setup ระหว่าง constructor กับ connectedCallback อย่างถูกต้องจึงสำคัญมาก
Ionic Framework — ใช้ pattern นี้อย่างเคร่งครัดในทุก component โดยเก็บเฉพาะ state เริ่มต้นไว้ใน constructor และย้าย logic ที่ต้องพึ่งพา DOM หรือ attribute ทั้งหมดไปไว้ใน connectedCallback เพื่อรองรับการถูก re-mount ซ้ำในแอปแบบ SPA