ข้ามไปยังเนื้อหา

Constructor และ connectedCallback

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 จะถูกเรียกทุกครั้งที่ 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 ถูกแทรกเข้าไปใหม่

ตัวเลือกBenefitCost
ตั้งค่าใน 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

ทำไมจึงไม่ควรจัดการ DOM ภายใน constructor?
connectedCallback ตอบสนองต่ออะไร?
connectedCallback สามารถถูกเรียกมากกว่าหนึ่งครั้งสำหรับ element instance เดียวกันได้เมื่อใด?