Now.js Framework Documentation
ComponentManager
ComponentManager
ภาพรวม
ComponentManager คือระบบจัดการ Component ใน Now.js Framework ที่ช่วยให้คุณสร้าง reusable components พร้อม lifecycle management, state management และ event handling
ใช้เมื่อ:
- ต้องการสร้าง reusable UI components
- ต้องการ component lifecycle (created, mounted, destroyed)
- ต้องการ state management ภายใน component
- ต้องการ event handling แบบ declarative
ทำไมต้องใช้:
- ✅ Component lifecycle hooks (8 hooks)
- ✅ Reactive state สำหรับ auto-update
- ✅ Template processing พร้อม data binding
- ✅ Event delegation และ custom events
- ✅ Props extraction จาก attributes
- ✅ Virtual DOM patching
การใช้งานพื้นฐาน
การสร้าง Component
// js/components/counter.js
ComponentManager.define('counter', {
// Template HTML
template: `
<div class="counter">
<span class="count">{{ count }}</span>
<button data-ref="increment">+</button>
<button data-ref="decrement">-</button>
</div>
`,
// Initial state
state: {
count: 0
},
// Methods
methods: {
increment() {
this.state.count++;
this.render();
},
decrement() {
this.state.count--;
this.render();
}
},
// Event handlers
events: {
'click [data-ref="increment"]': function(e) {
this.methods.increment();
},
'click [data-ref="decrement"]': function(e) {
this.methods.decrement();
}
},
// Lifecycle hooks
mounted() {
console.log('Counter mounted!', this.element);
}
});การใช้งานใน HTML
<!-- Basic usage -->
<div data-component="counter"></div>
<!-- With props -->
<div data-component="counter" data-count="10"></div>
<!-- With custom template -->
<div data-component="counter">
<div class="custom-counter">
<span>{{ count }}</span>
<button data-ref="increment">เพิ่ม</button>
</div>
</div>Lifecycle Hooks
ComponentManager มี 8 lifecycle hooks ตามลำดับการทำงาน:
graph TD
A[define] --> B[beforeCreate]
B --> C[created]
C --> D[beforeMount]
D --> E[processTemplate]
E --> F[mounted]
F --> G[State Change]
G --> H[beforeUpdate]
H --> I[updated]
G --> J[Component Removal]
J --> K[beforeDestroy]
K --> L[destroyed]1. beforeCreate
ถูกเรียกก่อนสร้าง instance (ยังไม่มี state)
ComponentManager.define('example', {
beforeCreate() {
console.log('Before create - this.state:', this.state); // {}
}
});2. created
ถูกเรียกหลัง instance ถูกสร้าง (มี state แล้ว)
ComponentManager.define('example', {
state: { count: 0 },
created() {
console.log('Created - count:', this.state.count); // 0
// ดี: fetch initial data
this.fetchData();
},
methods: {
async fetchData() {
const data = await fetch('/api/data').then(r => r.json());
this.state.data = data;
}
}
});3. beforeMount
ถูกเรียกก่อน template ถูก render ลง DOM
ComponentManager.define('example', {
beforeMount() {
// ปรับ state ก่อน render
this.state.timestamp = Date.now();
}
});4. mounted
ถูกเรียกหลัง component ใส่ลง DOM แล้ว
ComponentManager.define('chart', {
template: '<canvas id="chart"></canvas>',
mounted() {
// ดี: ทำงานกับ DOM elements
const canvas = this.element.querySelector('#chart');
this.chart = new Chart(canvas, this.state.chartConfig);
}
});5. beforeUpdate
ถูกเรียกก่อน re-render
ComponentManager.define('example', {
beforeUpdate() {
console.log('กำลังจะ update...');
this.state.lastUpdate = Date.now();
}
});6. updated
ถูกเรียกหลัง re-render เสร็จ
ComponentManager.define('example', {
updated() {
console.log('Update เสร็จแล้ว');
// ดี: scroll to new content
this.element.scrollIntoView({ behavior: 'smooth' });
}
});7. beforeDestroy
ถูกเรียกก่อน component ถูกลบ
ComponentManager.define('timer', {
state: { timer: null },
mounted() {
this.state.timer = setInterval(() => {
this.state.count++;
this.render();
}, 1000);
},
beforeDestroy() {
// ดี: cleanup resources
clearInterval(this.state.timer);
}
});8. destroyed
ถูกเรียกหลัง component ถูกลบออกจาก DOM
ComponentManager.define('example', {
destroyed() {
console.log('Component destroyed');
// Notify parent or analytics
EventManager.emit('component:destroyed', this.id);
}
});Component Definition Options
ตัวเลือกทั้งหมด
ComponentManager.define('complete-example', {
// Template
template: '<div class="example">{{ title }}</div>',
// Initial state
state: {
title: 'Hello',
items: []
},
// Props validation (optional)
props: {
title: { type: String, default: 'Default Title' },
count: { type: Number, default: 0 }
},
// Methods
methods: {
doSomething() {
this.state.title = 'Changed';
this.render();
}
},
// Computed properties
computed: {
fullTitle() {
return `${this.state.title} - App`;
}
},
// Watch state changes
watch: {
'state.count': function(newVal, oldVal) {
console.log(`count changed: ${oldVal} -> ${newVal}`);
}
},
// Reactive mode (auto re-render on state change)
reactive: true,
// Render strategy: 'auto', 'manual', 'batch'
renderStrategy: 'auto',
// Event handlers
events: {
'click button': function(e) { },
'submit form': function(e) { },
'custom:event': function(data) { }
},
// ARIA attributes
aria: {
role: 'region',
label: 'Example component'
},
// Custom element validation
validElement: (element) => element.tagName === 'DIV',
// Custom element setup
setupElement: (element, state) => {
element.classList.add('initialized');
},
// Error boundary
errorBoundary: true,
errorCaptured: (error) => {
console.error('Component error:', error);
return false; // Don't propagate
},
// Lifecycle hooks
beforeCreate() { },
created() { },
beforeMount() { },
mounted() { },
beforeUpdate() { },
updated() { },
beforeDestroy() { },
destroyed() { }
});State และ Reactivity
Basic State
ComponentManager.define('user-card', {
state: {
name: 'Guest',
email: '',
isActive: false
},
methods: {
updateName(newName) {
this.state.name = newName;
this.render(); // Manual re-render
}
}
});Reactive State
เมื่อเปิด reactive: true state จะ auto-trigger re-render:
ComponentManager.define('reactive-counter', {
reactive: true, // Enable reactivity
state: {
count: 0
},
events: {
'click [data-action="increment"]': function() {
this.state.count++; // Auto re-renders!
}
}
});Watch
ติดตามการเปลี่ยนแปลง state:
ComponentManager.define('watcher-example', {
reactive: true,
state: {
query: '',
results: []
},
watch: {
'state.query': async function(newVal, oldVal) {
if (newVal.length >= 3) {
this.state.results = await this.methods.search(newVal);
}
}
},
methods: {
async search(query) {
const response = await fetch(`/api/search?q=${query}`);
return response.json();
}
}
});Event Handling
Event Syntax
events: {
// Basic: 'eventType selector'
'click button': function(e) { },
// Multiple events
'mouseenter .card': function(e) { },
'mouseleave .card': function(e) { },
// Form events
'submit form': function(e) {
e.preventDefault();
},
'input [name="search"]': function(e) {
this.state.query = e.target.value;
},
// Custom events (via EventManager)
'user:login': function(userData) { },
'cart:updated': function(items) { }
}Event Delegation
Event ถูกผูกกับ root element และใช้ delegation:
ComponentManager.define('todo-list', {
template: `
<ul class="todos">
<li data-for="item in items">
<template>
<span data-text="item.title"></span>
<button data-action="delete" data-id="{{ item.id }}">ลบ</button>
</template>
</li>
</ul>
`,
events: {
// Handles click on any [data-action="delete"] inside component
'click [data-action="delete"]': function(e) {
const id = e.target.dataset.id;
this.methods.deleteItem(id);
}
}
});Props
การรับ Props
Props ถูก extract จาก data-* attributes:
<div data-component="user-card"
data-user-id="123"
data-name="John Doe"
data-count="5"
data-active="true">
</div>ComponentManager.define('user-card', {
created() {
console.log(this.props);
// { userId: "123", name: "John Doe", count: 5, active: true }
}
});Auto Type Conversion
- Numbers:
"123"→123 - Booleans:
"true"→true,"false"→false - Arrays:
"[1,2,3]"→[1, 2, 3]
Refs
เข้าถึง DOM elements ผ่าน refs:
<div data-component="form-example">
<input data-ref="nameInput" type="text">
<button data-ref="submitBtn">Submit</button>
</div>ComponentManager.define('form-example', {
mounted() {
// Access via refs proxy
this.refs.nameInput.focus();
this.refs.submitBtn.disabled = true;
},
methods: {
validate() {
const value = this.refs.nameInput.value;
this.refs.submitBtn.disabled = value.length < 3;
}
}
});API อ้างอิง
ComponentManager.define(name, definition)
ลงทะเบียน component definition
| Parameter | Type | Description |
|---|---|---|
name |
string | ชื่อ component (ต้องไม่ซ้ำ) |
definition |
object | Component definition object |
Returns: Object - Processed definition
const definition = ComponentManager.define('my-component', {
template: '<div>Hello</div>',
state: { count: 0 }
});ComponentManager.mount(element, name, props)
Mount component ลง element
| Parameter | Type | Description |
|---|---|---|
element |
HTMLElement | Target element |
name |
string | ชื่อ component |
props |
object | Initial props |
Returns: Promise<Object> - Component instance
const element = document.querySelector('#container');
const instance = await ComponentManager.mount(element, 'counter', { count: 10 });ComponentManager.destroy(element)
ลบ component ออกจาก element
| Parameter | Type | Description |
|---|---|---|
element |
HTMLElement | Element ที่มี component |
await ComponentManager.destroy(document.querySelector('#counter'));ComponentManager.has(name)
ตรวจสอบว่ามี component ลงทะเบียนไว้หรือไม่
| Parameter | Type | Description |
|---|---|---|
name |
string | ชื่อ component |
Returns: boolean
if (ComponentManager.has('counter')) {
console.log('Counter component is registered');
}ComponentManager.get(name)
รับ component definition
| Parameter | Type | Description |
|---|---|---|
name |
string | ชื่อ component |
Returns: Object|undefined - Component definition
const definition = ComponentManager.get('counter');
console.log(definition.state); // { count: 0 }ComponentManager.cleanup(container)
ลบ components ทั้งหมดใน container
| Parameter | Type | Description |
|---|---|---|
container |
HTMLElement | Container element |
await ComponentManager.cleanup(document.querySelector('#app'));ComponentManager.forceUpdate(instance)
บังคับ re-render component
| Parameter | Type | Description |
|---|---|---|
instance |
Object | Component instance |
ComponentManager.forceUpdate(instance);ตัวอย่างขั้นสูง
Component ที่โหลดข้อมูลจาก API
ComponentManager.define('user-list', {
template: `
<div class="user-list">
<div data-if="loading" class="loading">กำลังโหลด...</div>
<div data-if="error" class="error" data-text="error"></div>
<ul data-if="!loading && !error" data-for="user in users">
<template>
<li>
<img data-attr="src: user.avatar" alt="">
<span data-text="user.name"></span>
</li>
</template>
</ul>
</div>
`,
state: {
users: [],
loading: false,
error: null
},
async created() {
await this.methods.fetchUsers();
},
methods: {
async fetchUsers() {
this.state.loading = true;
this.state.error = null;
try {
const response = await fetch('/api/users');
if (!response.ok) throw new Error('Failed to fetch');
this.state.users = await response.json();
} catch (error) {
this.state.error = error.message;
} finally {
this.state.loading = false;
this.render();
}
}
}
});Nested Components
<div data-component="dashboard">
<div data-component="stats-card" data-title="Users" data-value="150"></div>
<div data-component="stats-card" data-title="Sales" data-value="$12,500"></div>
<div data-component="chart" data-type="line"></div>
</div>ComponentManager.define('stats-card', {
template: `
<div class="stats-card">
<h3 data-text="title"></h3>
<div class="value" data-text="value"></div>
</div>
`,
created() {
this.state.title = this.props.title;
this.state.value = this.props.value;
}
});ข้อควรระวัง
⚠️ 1. ชื่อ Component ต้องไม่ซ้ำ
// ❌ จะ warning และ overwrite
ComponentManager.define('counter', { /* ... */ }); // file1.js
ComponentManager.define('counter', { /* ... */ }); // file2.js
// ✅ ใช้ชื่อที่ unique
ComponentManager.define('page-counter', { /* ... */ });
ComponentManager.define('cart-counter', { /* ... */ });⚠️ 2. อย่าลืม cleanup resources
// ❌ Memory leak
ComponentManager.define('timer', {
mounted() {
setInterval(() => { /* ... */ }, 1000);
}
});
// ✅ Cleanup properly
ComponentManager.define('timer', {
mounted() {
this.state.timer = setInterval(() => { /* ... */ }, 1000);
},
beforeDestroy() {
clearInterval(this.state.timer);
}
});⚠️ 3. ใช้ this.render() สำหรับ manual update
// ❌ ไม่มีผล - DOM ไม่ update
ComponentManager.define('counter', {
methods: {
increment() {
this.state.count++;
// missing render()
}
}
});
// ✅ เรียก render() หลังเปลี่ยน state
ComponentManager.define('counter', {
methods: {
increment() {
this.state.count++;
this.render(); // Update DOM
}
}
});
// ✅ หรือใช้ reactive: true
ComponentManager.define('counter', {
reactive: true, // Auto renders on state change
methods: {
increment() {
this.state.count++; // Auto renders!
}
}
});เอกสารที่เกี่ยวข้อง
- TemplateManager - Template และ data binding
- ReactiveManager - Reactive state management
- EventManager - Event handling
- ApiComponent - API-driven components