Web Components
Web Components เป็น Web Standard สร้าง Reusable UI Components ทำงานได้ทุก Browser ทุก Framework Custom Elements Shadow DOM HTML Templates ES Modules
เตรียมตัวสัมภาษณ์งาน ต้องเข้าใจ Core APIs Lifecycle Callbacks Best Practices และสามารถเขียน Code ได้จริง
Core Web Components APIs
// === Web Components — Core APIs ===
// 1. Custom Element — Basic
class MyCounter extends HTMLElement {
constructor() {
super();
this._count = 0;
this.attachShadow({ mode: 'open' });
}
// Lifecycle Callbacks
connectedCallback() {
// เรียกเมื่อ Element ถูกเพิ่มเข้า DOM
this.render();
console.log('MyCounter connected to DOM');
}
disconnectedCallback() {
// เรียกเมื่อ Element ถูกลบออกจาก DOM
console.log('MyCounter removed from DOM');
}
// Observed Attributes
static get observedAttributes() {
return ['initial-count', 'step'];
}
attributeChangedCallback(name, oldVal, newVal) {
// เรียกเมื่อ Attribute เปลี่ยน
if (name === 'initial-count') {
this._count = parseInt(newVal) || 0;
}
this.render();
}
// Getters/Setters (Properties)
get count() { return this._count; }
set count(val) {
this._count = val;
this.render();
this.dispatchEvent(new CustomEvent('count-changed', {
detail: { count: this._count },
bubbles: true,
composed: true, // ผ่าน Shadow DOM boundary
}));
}
increment() {
const step = parseInt(this.getAttribute('step')) || 1;
this.count = this._count + step;
}
decrement() {
const step = parseInt(this.getAttribute('step')) || 1;
this.count = this._count - step;
}
render() {
this.shadowRoot.innerHTML = `
`;
this.shadowRoot.getElementById('inc')
.addEventListener('click', () => this.increment());
this.shadowRoot.getElementById('dec')
.addEventListener('click', () => this.decrement());
}
}
// Register
customElements.define('my-counter', MyCounter);
// 2. HTML Template + Slot
//
//
//
// Default Header
// Default Content
//
//
//
class MyCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
// const template = document.getElementById('card-template');
// shadow.appendChild(template.content.cloneNode(true));
shadow.innerHTML = `
`;
}
}
customElements.define('my-card', MyCard);
// Usage:
//
// Title
// Content goes here
// Footer
//
console.log('Web Components registered:');
console.log(' — Counter with Shadow DOM');
console.log(' — Card with Slots');
Interview Questions
// interview_questions.js — Web Components Interview Q&A
const questions = [
{
q: "Web Components ประกอบด้วยอะไรบ้าง",
a: "4 เทคโนโลยี: Custom Elements, Shadow DOM, HTML Templates, ES Modules",
difficulty: "basic",
},
{
q: "Lifecycle Callbacks มีอะไรบ้าง",
a: `constructor() — สร้าง Element
connectedCallback() — เพิ่มเข้า DOM
disconnectedCallback() — ลบออกจาก DOM
attributeChangedCallback(name, old, new) — Attribute เปลี่ยน
adoptedCallback() — ย้ายไปอีก Document`,
difficulty: "basic",
},
{
q: "Shadow DOM open กับ closed ต่างกันอย่างไร",
a: `open: เข้าถึง shadowRoot จากภายนอกได้ (element.shadowRoot)
closed: เข้าถึง shadowRoot จากภายนอกไม่ได้ (return null)
ส่วนใหญ่ใช้ open เพราะยืดหยุ่นกว่า`,
difficulty: "intermediate",
},
{
q: ":host และ :host() ใช้อย่างไร",
a: `:host — Style ตัว Host Element เอง
:host(.active) — Style เมื่อ Host มี class .active
:host-context(.dark) — Style เมื่ออยู่ใน Context .dark
::slotted(p) — Style slotted elements`,
difficulty: "intermediate",
},
{
q: "Custom Event ส่งผ่าน Shadow DOM ได้อย่างไร",
a: `ใช้ composed: true ใน CustomEvent options
new CustomEvent('my-event', {
detail: { data: 'value' },
bubbles: true,
composed: true // ผ่าน Shadow DOM boundary
})`,
difficulty: "advanced",
},
{
q: "Web Components กับ Framework (React/Vue) ใช้ร่วมกันได้หรือไม่",
a: `ได้ — Web Components เป็น Standard ใช้ใน React, Vue, Angular ได้
React: ใช้ ref สำหรับ Properties, addEventListener สำหรับ Events
Vue: ใช้ v-bind สำหรับ Properties, @ สำหรับ Events
Angular: ใช้ CUSTOM_ELEMENTS_SCHEMA`,
difficulty: "advanced",
},
{
q: "Form-associated Custom Elements คืออะไร",
a: `Custom Elements ที่ทำงานร่วมกับ HTML Form ได้
ใช้ static formAssociated = true
ใช้ ElementInternals API (this.attachInternals())
สามารถ setFormValue, reportValidity ได้`,
difficulty: "advanced",
},
];
console.log("Web Components Interview Questions:");
console.log("=" .repeat(55));
for (const { q, a, difficulty } of questions) {
const icon = difficulty === 'basic' ? 'B' :
difficulty === 'intermediate' ? 'I' : 'A';
console.log(`\n [] Q: `);
console.log(` A: `);
}
// Performance Tips
const tips = {
"Lazy Rendering": "render ใน connectedCallback ไม่ใช่ constructor",
"Event Delegation": "ใช้ Event Delegation แทน addEventListener ทุก Element",
"Attribute vs Property": "Attribute = string เท่านั้น, Property = any type",
"observedAttributes": "ต้อง return array ใน static get observedAttributes",
"Memory Leaks": "cleanup ใน disconnectedCallback ลบ Event Listeners",
};
console.log("\n\nPerformance Tips:");
for (const [tip, desc] of Object.entries(tips)) {
console.log(` : `);
}
Advanced Patterns
// advanced_patterns.js — Advanced Web Component Patterns
// 1. Mixin Pattern
const EventEmitterMixin = (Base) => class extends Base {
_listeners = new Map();
on(event, callback) {
if (!this._listeners.has(event)) {
this._listeners.set(event, []);
}
this._listeners.get(event).push(callback);
}
emit(event, data) {
const callbacks = this._listeners.get(event) || [];
callbacks.forEach(cb => cb(data));
this.dispatchEvent(new CustomEvent(event, {
detail: data, bubbles: true, composed: true,
}));
}
};
// 2. Reactive Properties Pattern
class ReactiveElement extends HTMLElement {
static get properties() { return {}; }
constructor() {
super();
this.attachShadow({ mode: 'open' });
this._props = {};
// Create reactive properties
const props = this.constructor.properties;
for (const [name, config] of Object.entries(props)) {
const defaultVal = config.default !== undefined ? config.default : null;
this._props[name] = defaultVal;
Object.defineProperty(this, name, {
get: () => this._props[name],
set: (val) => {
const old = this._props[name];
this._props[name] = val;
if (old !== val) {
this.requestUpdate();
}
},
});
}
}
requestUpdate() {
if (!this._updateQueued) {
this._updateQueued = true;
Promise.resolve().then(() => {
this._updateQueued = false;
this.render();
});
}
}
connectedCallback() { this.render(); }
render() { /* Override in subclass */ }
}
// 3. Usage
class TodoList extends ReactiveElement {
static get properties() {
return {
items: { default: [] },
filter: { default: 'all' },
};
}
render() {
const filtered = this.filter === 'all' ? this.items :
this.items.filter(i => this.filter === 'done' ? i.done : !i.done);
this.shadowRoot.innerHTML = `
"
data-filter="">
`).join('')}
" data-index="">
`).join('')}
items
`;
// Event Delegation
this.shadowRoot.addEventListener('click', (e) => {
if (e.target.dataset.filter) {
this.filter = e.target.dataset.filter;
}
if (e.target.dataset.index !== undefined) {
const idx = parseInt(e.target.dataset.index);
this.items = this.items.map((item, i) =>
i === idx ? { ...item, done: !item.done } : item
);
}
});
}
}
customElements.define('todo-list', TodoList);
// Testing Web Components
// const el = document.createElement('todo-list');
// el.items = [
// { text: 'Learn Web Components', done: true },
// { text: 'Build Custom Elements', done: false },
// { text: 'Master Shadow DOM', done: false },
// ];
// document.body.appendChild(el);
console.log("Advanced Patterns:");
console.log(" 1. Mixin Pattern — EventEmitterMixin");
console.log(" 2. Reactive Properties — Auto re-render on change");
console.log(" 3. Event Delegation — Single listener on shadowRoot");
console.log(" 4. Batched Updates — requestUpdate with microtask");
เคล็ดลับสัมภาษณ์
- Lifecycle: เข้าใจ constructor, connectedCallback, disconnectedCallback, attributeChangedCallback
- Shadow DOM: อธิบายความแตกต่าง open vs closed และ CSS Encapsulation
- Slots: อธิบาย named slots, default content, ::slotted() selector
- Events: อธิบาย composed: true สำหรับส่ง Events ผ่าน Shadow DOM
- Performance: render ใน connectedCallback ใช้ Event Delegation
- Framework Integration: อธิบายวิธีใช้ร่วมกับ React, Vue, Angular
Web Components คืออะไร
Web Standard 4 เทคโนโลยี Custom Elements Shadow DOM HTML Templates ES Modules สร้าง Reusable UI Components ทุก Browser ทุก Framework
Shadow DOM คืออะไร
สร้าง DOM Tree แยก Main DOM CSS JavaScript ภายในไม่กระทบภายนอก ภายนอกไม่กระทบภายใน ป้องกัน Style Leaking attachShadow mode open
Custom Elements มีกี่แบบ
2 แบบ Autonomous สร้างจาก HTMLElement ใหม่ทั้งหมด Customized Built-in สร้างจาก Element มีอยู่ extends HTMLButtonElement ใช้ is attribute
Web Components เทียบกับ React Components อย่างไร
WC Web Standard ทุก Framework ไม่พึ่ง Library ขนาดเล็ก ไม่มี State Management Virtual DOM React JSX State Hooks Virtual DOM Ecosystem ใหญ่ ต้องพึ่ง Library ใช้ร่วมกันได้
สรุป
Web Components เป็น Web Standard Custom Elements Shadow DOM HTML Templates เตรียมสัมภาษณ์ต้องเข้าใจ Lifecycle Callbacks Shadow DOM Encapsulation Slots Events composed Advanced Patterns Mixin Reactive Properties ใช้ร่วมกับ React Vue Angular ได้
